mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 09:17:43 +03:00
Move closureConvert and validate compiler phases into separate files. (#11656)
CHANGELOG_BEGIN CHANGELOG_END
This commit is contained in:
parent
87f282c7f3
commit
7296ba4dfb
@ -0,0 +1,194 @@
|
||||
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.lf.speedy
|
||||
|
||||
/** Closure Conversion (Phase of the speedy compiler pipeline)
|
||||
*
|
||||
* This compilation phase transforms from SExpr0 to SExpr0.
|
||||
* SExpr0 contains expression forms which exist during the speedy compilation pipeline.
|
||||
*
|
||||
* TODO: introduces new expression type (SExpr1) for the result of this phase, and input to the
|
||||
* following ANF transformation phase.
|
||||
*/
|
||||
|
||||
import com.daml.lf.speedy.{SExpr0 => s}
|
||||
|
||||
private[speedy] object ClosureConversion {
|
||||
|
||||
case class CompilationError(error: String) extends RuntimeException(error, null, true, false)
|
||||
|
||||
/** Convert abstractions in a speedy expression into
|
||||
* explicit closure creations.
|
||||
* This step computes the free variables in an abstraction
|
||||
* body, then translates the references in the body into
|
||||
* references to the immediate top of the argument stack,
|
||||
* and changes the abstraction into a closure creation node
|
||||
* describing the free variables that need to be captured.
|
||||
*
|
||||
* For example:
|
||||
* SELet(..two-bindings..) in
|
||||
* SEAbs(2,
|
||||
* SEVar(4) .. [reference to first let-bound variable]
|
||||
* SEVar(2)) [reference to first function-arg]
|
||||
* =>
|
||||
* SELet(..two-bindings..) in
|
||||
* SEMakeClo(
|
||||
* Array(SELocS(2)), [capture the first let-bound variable, from the stack]
|
||||
* 2,
|
||||
* SELocF(0) .. [reference the first let-bound variable via the closure]
|
||||
* SELocA(0)) [reference the first function arg]
|
||||
*/
|
||||
|
||||
// TODO: Introduce a new type expression for the result of closure conversion
|
||||
private[speedy] def closureConvert(expr: s.SExpr): s.SExpr = {
|
||||
closureConvert(Map.empty, expr)
|
||||
}
|
||||
|
||||
private def closureConvert(remaps: Map[Int, s.SELoc], expr: s.SExpr): s.SExpr = {
|
||||
|
||||
// remaps is a function which maps the relative offset from variables (SEVar) to their runtime location
|
||||
// The Map must contain a binding for every variable referenced.
|
||||
// The Map is consulted when translating variable references (SEVar) and free variables of an abstraction (SEAbs)
|
||||
def remap(i: Int): s.SELoc =
|
||||
remaps.get(i) match {
|
||||
case Some(loc) => loc
|
||||
case None =>
|
||||
throw CompilationError(s"remap($i),remaps=$remaps")
|
||||
}
|
||||
expr match {
|
||||
case s.SEVar(i) => remap(i)
|
||||
case v: s.SEVal => v
|
||||
case be: s.SEBuiltin => be
|
||||
case pl: s.SEValue => pl
|
||||
case f: s.SEBuiltinRecursiveDefinition => f
|
||||
case s.SELocation(loc, body) =>
|
||||
s.SELocation(loc, closureConvert(remaps, body))
|
||||
|
||||
case s.SEAbs(0, _) =>
|
||||
throw CompilationError("empty SEAbs")
|
||||
|
||||
case s.SEAbs(arity, body) =>
|
||||
val fvs = freeVars(body, arity).toList.sorted
|
||||
val newRemapsF: Map[Int, s.SELoc] = fvs.zipWithIndex.map { case (orig, i) =>
|
||||
(orig + arity) -> s.SELocF(i)
|
||||
}.toMap
|
||||
val newRemapsA = (1 to arity).map { case i =>
|
||||
i -> s.SELocA(arity - i)
|
||||
}
|
||||
// The keys in newRemapsF and newRemapsA are disjoint
|
||||
val newBody = closureConvert(newRemapsF ++ newRemapsA, body)
|
||||
s.SEMakeClo(fvs.map(remap).toArray, arity, newBody)
|
||||
|
||||
case s.SEAppGeneral(fun, args) =>
|
||||
val newFun = closureConvert(remaps, fun)
|
||||
val newArgs = args.map(closureConvert(remaps, _))
|
||||
s.SEApp(newFun, newArgs)
|
||||
|
||||
case s.SECase(scrut, alts) =>
|
||||
s.SECase(
|
||||
closureConvert(remaps, scrut),
|
||||
alts.map { case s.SCaseAlt(pat, body) =>
|
||||
val n = pat.numArgs
|
||||
s.SCaseAlt(
|
||||
pat,
|
||||
closureConvert(shift(remaps, n), body),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
case s.SELet(bounds, body) =>
|
||||
s.SELet(
|
||||
bounds.zipWithIndex.map { case (b, i) =>
|
||||
closureConvert(shift(remaps, i), b)
|
||||
},
|
||||
closureConvert(shift(remaps, bounds.length), body),
|
||||
)
|
||||
|
||||
case s.SETryCatch(body, handler) =>
|
||||
s.SETryCatch(
|
||||
closureConvert(remaps, body),
|
||||
closureConvert(shift(remaps, 1), handler),
|
||||
)
|
||||
|
||||
case s.SEScopeExercise(body) =>
|
||||
s.SEScopeExercise(closureConvert(remaps, body))
|
||||
|
||||
case s.SELabelClosure(label, expr) =>
|
||||
s.SELabelClosure(label, closureConvert(remaps, expr))
|
||||
|
||||
case s.SELet1General(bound, body) =>
|
||||
s.SELet1General(closureConvert(remaps, bound), closureConvert(shift(remaps, 1), body))
|
||||
|
||||
case _: s.SELoc | _: s.SEMakeClo | _: s.SEDamlException | _: s.SEImportValue =>
|
||||
throw CompilationError(s"closureConvert: unexpected $expr")
|
||||
}
|
||||
}
|
||||
|
||||
// Modify/extend `remaps` to reflect when new values are pushed on the stack. This
|
||||
// happens as we traverse into SELet and SECase bodies which have bindings which at
|
||||
// runtime will appear on the stack.
|
||||
// We must modify `remaps` because it is keyed by indexes relative to the end of the stack.
|
||||
// And any values in the map which are of the form SELocS must also be _shifted_
|
||||
// because SELocS indexes are also relative to the end of the stack.
|
||||
private[this] def shift(remaps: Map[Int, s.SELoc], n: Int): Map[Int, s.SELoc] = {
|
||||
|
||||
// We must update both the keys of the map (the relative-indexes from the original SEVar)
|
||||
// And also any values in the map which are stack located (SELocS), which are also indexed relatively
|
||||
val m1 = remaps.map { case (k, loc) => (n + k, shiftLoc(loc, n)) }
|
||||
|
||||
// And create mappings for the `n` new stack items
|
||||
val m2 = (1 to n).map(i => (i, s.SELocS(i)))
|
||||
|
||||
m1 ++ m2
|
||||
}
|
||||
|
||||
private[this] def shiftLoc(loc: s.SELoc, n: Int): s.SELoc = loc match {
|
||||
case s.SELocS(i) => s.SELocS(i + n)
|
||||
case s.SELocA(_) | s.SELocF(_) => loc
|
||||
}
|
||||
|
||||
/** Compute the free variables in a speedy expression.
|
||||
* The returned free variables are de bruijn indices
|
||||
* adjusted to the stack of the caller.
|
||||
*/
|
||||
private[this] def freeVars(expr: s.SExpr, initiallyBound: Int): Set[Int] = {
|
||||
def go(expr: s.SExpr, bound: Int, free: Set[Int]): Set[Int] =
|
||||
expr match {
|
||||
case s.SEVar(i) =>
|
||||
if (i > bound) free + (i - bound) else free /* adjust to caller's environment */
|
||||
case _: s.SEVal => free
|
||||
case _: s.SEBuiltin => free
|
||||
case _: s.SEValue => free
|
||||
case _: s.SEBuiltinRecursiveDefinition => free
|
||||
case s.SELocation(_, body) =>
|
||||
go(body, bound, free)
|
||||
case s.SEAppGeneral(fun, args) =>
|
||||
args.foldLeft(go(fun, bound, free))((acc, arg) => go(arg, bound, acc))
|
||||
case s.SEAbs(n, body) =>
|
||||
go(body, bound + n, free)
|
||||
case s.SECase(scrut, alts) =>
|
||||
alts.foldLeft(go(scrut, bound, free)) { case (acc, s.SCaseAlt(pat, body)) =>
|
||||
val n = pat.numArgs
|
||||
go(body, bound + n, acc)
|
||||
}
|
||||
case s.SELet(bounds, body) =>
|
||||
bounds.zipWithIndex.foldLeft(go(body, bound + bounds.length, free)) {
|
||||
case (acc, (expr, idx)) => go(expr, bound + idx, acc)
|
||||
}
|
||||
case s.SELabelClosure(_, expr) =>
|
||||
go(expr, bound, free)
|
||||
case s.SETryCatch(body, handler) =>
|
||||
go(body, bound, go(handler, 1 + bound, free))
|
||||
case s.SEScopeExercise(body) =>
|
||||
go(body, bound, free)
|
||||
|
||||
case _: s.SELoc | _: s.SEMakeClo | _: s.SEDamlException | _: s.SEImportValue |
|
||||
_: s.SELet1General =>
|
||||
throw CompilationError(s"freeVars: unexpected $expr")
|
||||
}
|
||||
|
||||
go(expr, initiallyBound, Set.empty)
|
||||
}
|
||||
|
||||
}
|
@ -15,6 +15,8 @@ import com.daml.lf.speedy.SBuiltin._
|
||||
import com.daml.lf.speedy.SValue._
|
||||
import com.daml.lf.speedy.{SExpr0 => s}
|
||||
import com.daml.lf.speedy.{SExpr => t}
|
||||
import com.daml.lf.speedy.ClosureConversion.closureConvert
|
||||
import com.daml.lf.speedy.ValidateCompilation.validateCompilation
|
||||
import com.daml.lf.validation.{EUnknownDefinition, Validation, ValidationError}
|
||||
import com.daml.scalautil.Statement.discard
|
||||
import com.daml.nameof.NameOf
|
||||
@ -323,28 +325,28 @@ private[lf] final class Compiler(
|
||||
@throws[PackageNotFound]
|
||||
@throws[CompilationError]
|
||||
def unsafeCompile(cmds: ImmArray[Command]): t.SExpr =
|
||||
validate(compilationPipeline(compileCommands(cmds)))
|
||||
validateCompilation(compilationPipeline(compileCommands(cmds)))
|
||||
|
||||
@throws[PackageNotFound]
|
||||
@throws[CompilationError]
|
||||
def unsafeCompileForReinterpretation(cmd: Command): t.SExpr =
|
||||
validate(compilationPipeline(compileCommandForReinterpretation(cmd)))
|
||||
validateCompilation(compilationPipeline(compileCommandForReinterpretation(cmd)))
|
||||
|
||||
@throws[PackageNotFound]
|
||||
@throws[CompilationError]
|
||||
def unsafeCompile(expr: Expr): t.SExpr =
|
||||
validate(compilationPipeline(compile(Env.Empty, expr)))
|
||||
validateCompilation(compilationPipeline(compile(Env.Empty, expr)))
|
||||
|
||||
@throws[PackageNotFound]
|
||||
@throws[CompilationError]
|
||||
def unsafeClosureConvert(sexpr: s.SExpr): t.SExpr =
|
||||
validate(compilationPipeline(sexpr))
|
||||
validateCompilation(compilationPipeline(sexpr))
|
||||
|
||||
// Run the compilation pipeline phases:
|
||||
// (1) closure conversion
|
||||
// (2) transform to ANF
|
||||
private[this] def compilationPipeline(sexpr: s.SExpr): t.SExpr =
|
||||
flattenToAnf(closureConvert(Map.empty, sexpr))
|
||||
flattenToAnf(closureConvert(sexpr))
|
||||
|
||||
@throws[PackageNotFound]
|
||||
@throws[CompilationError]
|
||||
@ -453,12 +455,6 @@ private[lf] final class Compiler(
|
||||
result
|
||||
}
|
||||
|
||||
private[this] def patternNArgs(pat: t.SCasePat): Int = pat match {
|
||||
case _: t.SCPEnum | _: t.SCPPrimCon | t.SCPNil | t.SCPDefault | t.SCPNone => 0
|
||||
case _: t.SCPVariant | t.SCPSome => 1
|
||||
case t.SCPCons => 2
|
||||
}
|
||||
|
||||
private[this] def compile(env: Env, expr0: Expr): s.SExpr =
|
||||
expr0 match {
|
||||
case EVar(name) =>
|
||||
@ -1191,273 +1187,6 @@ private[lf] final class Compiler(
|
||||
case _ => expr
|
||||
}
|
||||
|
||||
/** Convert abstractions in a speedy expression into
|
||||
* explicit closure creations.
|
||||
* This step computes the free variables in an abstraction
|
||||
* body, then translates the references in the body into
|
||||
* references to the immediate top of the argument stack,
|
||||
* and changes the abstraction into a closure creation node
|
||||
* describing the free variables that need to be captured.
|
||||
*
|
||||
* For example:
|
||||
* SELet(..two-bindings..) in
|
||||
* SEAbs(2,
|
||||
* SEVar(4) .. [reference to first let-bound variable]
|
||||
* SEVar(2)) [reference to first function-arg]
|
||||
* =>
|
||||
* SELet(..two-bindings..) in
|
||||
* SEMakeClo(
|
||||
* Array(SELocS(2)), [capture the first let-bound variable, from the stack]
|
||||
* 2,
|
||||
* SELocF(0) .. [reference the first let-bound variable via the closure]
|
||||
* SELocA(0)) [reference the first function arg]
|
||||
*/
|
||||
private[this] def closureConvert(remaps: Map[Int, s.SELoc], expr: s.SExpr): s.SExpr = {
|
||||
// remaps is a function which maps the relative offset from variables (SEVar) to their runtime location
|
||||
// The Map must contain a binding for every variable referenced.
|
||||
// The Map is consulted when translating variable references (SEVar) and free variables of an abstraction (SEAbs)
|
||||
def remap(i: Int): s.SELoc =
|
||||
remaps.get(i) match {
|
||||
case Some(loc) => loc
|
||||
case None =>
|
||||
throw CompilationError(s"remap($i),remaps=$remaps")
|
||||
}
|
||||
expr match {
|
||||
case s.SEVar(i) => remap(i)
|
||||
case v: s.SEVal => v
|
||||
case be: s.SEBuiltin => be
|
||||
case pl: s.SEValue => pl
|
||||
case f: s.SEBuiltinRecursiveDefinition => f
|
||||
case s.SELocation(loc, body) =>
|
||||
s.SELocation(loc, closureConvert(remaps, body))
|
||||
|
||||
case s.SEAbs(0, _) =>
|
||||
throw CompilationError("empty SEAbs")
|
||||
|
||||
case s.SEAbs(arity, body) =>
|
||||
val fvs = freeVars(body, arity).toList.sorted
|
||||
val newRemapsF: Map[Int, s.SELoc] = fvs.zipWithIndex.map { case (orig, i) =>
|
||||
(orig + arity) -> s.SELocF(i)
|
||||
}.toMap
|
||||
val newRemapsA = (1 to arity).map { case i =>
|
||||
i -> s.SELocA(arity - i)
|
||||
}
|
||||
// The keys in newRemapsF and newRemapsA are disjoint
|
||||
val newBody = closureConvert(newRemapsF ++ newRemapsA, body)
|
||||
s.SEMakeClo(fvs.map(remap).toArray, arity, newBody)
|
||||
|
||||
case s.SEAppGeneral(fun, args) =>
|
||||
val newFun = closureConvert(remaps, fun)
|
||||
val newArgs = args.map(closureConvert(remaps, _))
|
||||
s.SEApp(newFun, newArgs)
|
||||
|
||||
case s.SECase(scrut, alts) =>
|
||||
s.SECase(
|
||||
closureConvert(remaps, scrut),
|
||||
alts.map { case s.SCaseAlt(pat, body) =>
|
||||
val n = patternNArgs(pat)
|
||||
s.SCaseAlt(
|
||||
pat,
|
||||
closureConvert(shift(remaps, n), body),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
case s.SELet(bounds, body) =>
|
||||
s.SELet(
|
||||
bounds.zipWithIndex.map { case (b, i) =>
|
||||
closureConvert(shift(remaps, i), b)
|
||||
},
|
||||
closureConvert(shift(remaps, bounds.length), body),
|
||||
)
|
||||
|
||||
case s.SETryCatch(body, handler) =>
|
||||
s.SETryCatch(
|
||||
closureConvert(remaps, body),
|
||||
closureConvert(shift(remaps, 1), handler),
|
||||
)
|
||||
|
||||
case s.SEScopeExercise(body) =>
|
||||
s.SEScopeExercise(closureConvert(remaps, body))
|
||||
|
||||
case s.SELabelClosure(label, expr) =>
|
||||
s.SELabelClosure(label, closureConvert(remaps, expr))
|
||||
|
||||
case s.SELet1General(bound, body) =>
|
||||
s.SELet1General(closureConvert(remaps, bound), closureConvert(shift(remaps, 1), body))
|
||||
|
||||
case _: s.SELoc | _: s.SEMakeClo | _: s.SEDamlException | _: s.SEImportValue =>
|
||||
throw CompilationError(s"closureConvert: unexpected $expr")
|
||||
}
|
||||
}
|
||||
|
||||
// Modify/extend `remaps` to reflect when new values are pushed on the stack. This
|
||||
// happens as we traverse into SELet and SECase bodies which have bindings which at
|
||||
// runtime will appear on the stack.
|
||||
// We must modify `remaps` because it is keyed by indexes relative to the end of the stack.
|
||||
// And any values in the map which are of the form SELocS must also be _shifted_
|
||||
// because SELocS indexes are also relative to the end of the stack.
|
||||
private[this] def shift(remaps: Map[Int, s.SELoc], n: Int): Map[Int, s.SELoc] = {
|
||||
|
||||
// We must update both the keys of the map (the relative-indexes from the original SEVar)
|
||||
// And also any values in the map which are stack located (SELocS), which are also indexed relatively
|
||||
val m1 = remaps.map { case (k, loc) => (n + k, shiftLoc(loc, n)) }
|
||||
|
||||
// And create mappings for the `n` new stack items
|
||||
val m2 = (1 to n).map(i => (i, s.SELocS(i)))
|
||||
|
||||
m1 ++ m2
|
||||
}
|
||||
|
||||
private[this] def shiftLoc(loc: s.SELoc, n: Int): s.SELoc = loc match {
|
||||
case s.SELocS(i) => s.SELocS(i + n)
|
||||
case s.SELocA(_) | s.SELocF(_) => loc
|
||||
}
|
||||
|
||||
/** Compute the free variables in a speedy expression.
|
||||
* The returned free variables are de bruijn indices
|
||||
* adjusted to the stack of the caller.
|
||||
*/
|
||||
private[this] def freeVars(expr: s.SExpr, initiallyBound: Int): Set[Int] = {
|
||||
def go(expr: s.SExpr, bound: Int, free: Set[Int]): Set[Int] =
|
||||
expr match {
|
||||
case s.SEVar(i) =>
|
||||
if (i > bound) free + (i - bound) else free /* adjust to caller's environment */
|
||||
case _: s.SEVal => free
|
||||
case _: s.SEBuiltin => free
|
||||
case _: s.SEValue => free
|
||||
case _: s.SEBuiltinRecursiveDefinition => free
|
||||
case s.SELocation(_, body) =>
|
||||
go(body, bound, free)
|
||||
case s.SEAppGeneral(fun, args) =>
|
||||
args.foldLeft(go(fun, bound, free))((acc, arg) => go(arg, bound, acc))
|
||||
case s.SEAbs(n, body) =>
|
||||
go(body, bound + n, free)
|
||||
case s.SECase(scrut, alts) =>
|
||||
alts.foldLeft(go(scrut, bound, free)) { case (acc, s.SCaseAlt(pat, body)) =>
|
||||
go(body, bound + patternNArgs(pat), acc)
|
||||
}
|
||||
case s.SELet(bounds, body) =>
|
||||
bounds.zipWithIndex.foldLeft(go(body, bound + bounds.length, free)) {
|
||||
case (acc, (expr, idx)) => go(expr, bound + idx, acc)
|
||||
}
|
||||
case s.SELabelClosure(_, expr) =>
|
||||
go(expr, bound, free)
|
||||
case s.SETryCatch(body, handler) =>
|
||||
go(body, bound, go(handler, 1 + bound, free))
|
||||
case s.SEScopeExercise(body) =>
|
||||
go(body, bound, free)
|
||||
|
||||
case _: s.SELoc | _: s.SEMakeClo | _: s.SEDamlException | _: s.SEImportValue |
|
||||
_: s.SELet1General =>
|
||||
throw CompilationError(s"freeVars: unexpected $expr")
|
||||
}
|
||||
|
||||
go(expr, initiallyBound, Set.empty)
|
||||
}
|
||||
|
||||
/** Validate variable references in a speedy expression */
|
||||
// validate that we correctly captured all free-variables, and so reference to them is
|
||||
// via the surrounding closure, instead of just finding them higher up on the stack
|
||||
private[this] def validate(expr0: t.SExpr): t.SExpr = {
|
||||
|
||||
def goV(v: SValue): Unit =
|
||||
v match {
|
||||
case _: SPrimLit | STNat(_) | STypeRep(_) =>
|
||||
case SList(a) => a.iterator.foreach(goV)
|
||||
case SOptional(x) => x.foreach(goV)
|
||||
case SMap(_, entries) =>
|
||||
entries.foreach { case (k, v) =>
|
||||
goV(k)
|
||||
goV(v)
|
||||
}
|
||||
case SRecord(_, _, args) => args.forEach(goV)
|
||||
case SVariant(_, _, _, value) => goV(value)
|
||||
case SEnum(_, _, _) => ()
|
||||
case SAny(_, v) => goV(v)
|
||||
case _: SPAP | SToken | SStruct(_, _) =>
|
||||
throw CompilationError("validate: unexpected s.SEValue")
|
||||
}
|
||||
|
||||
def goBody(maxS: Int, maxA: Int, maxF: Int): t.SExpr => Unit = {
|
||||
|
||||
def goLoc(loc: t.SELoc) = loc match {
|
||||
case t.SELocS(i) =>
|
||||
if (i < 1 || i > maxS)
|
||||
throw CompilationError(s"validate: SELocS: index $i out of range ($maxS..1)")
|
||||
case t.SELocA(i) =>
|
||||
if (i < 0 || i >= maxA)
|
||||
throw CompilationError(s"validate: SELocA: index $i out of range (0..$maxA-1)")
|
||||
case t.SELocF(i) =>
|
||||
if (i < 0 || i >= maxF)
|
||||
throw CompilationError(s"validate: SELocF: index $i out of range (0..$maxF-1)")
|
||||
}
|
||||
|
||||
def go(expr: t.SExpr): Unit = expr match {
|
||||
case loc: t.SELoc => goLoc(loc)
|
||||
case _: t.SEVal => ()
|
||||
case _: t.SEBuiltin => ()
|
||||
case _: t.SEBuiltinRecursiveDefinition => ()
|
||||
case t.SEValue(v) => goV(v)
|
||||
case t.SEAppAtomicGeneral(fun, args) =>
|
||||
go(fun)
|
||||
args.foreach(go)
|
||||
case t.SEAppAtomicSaturatedBuiltin(_, args) =>
|
||||
args.foreach(go)
|
||||
case t.SEAppGeneral(fun, args) =>
|
||||
go(fun)
|
||||
args.foreach(go)
|
||||
case t.SEAppAtomicFun(fun, args) =>
|
||||
go(fun)
|
||||
args.foreach(go)
|
||||
case t.SEMakeClo(fvs, n, body) =>
|
||||
fvs.foreach(goLoc)
|
||||
goBody(0, n, fvs.length)(body)
|
||||
case t.SECaseAtomic(scrut, alts) =>
|
||||
go(scrut)
|
||||
alts.foreach { case t.SCaseAlt(pat, body) =>
|
||||
val n = patternNArgs(pat)
|
||||
goBody(maxS + n, maxA, maxF)(body)
|
||||
}
|
||||
case _: t.SELet1General => goLets(maxS)(expr)
|
||||
case _: t.SELet1Builtin => goLets(maxS)(expr)
|
||||
case _: t.SELet1BuiltinArithmetic => goLets(maxS)(expr)
|
||||
case t.SELocation(_, body) =>
|
||||
go(body)
|
||||
case t.SELabelClosure(_, expr) =>
|
||||
go(expr)
|
||||
case t.SETryCatch(body, handler) =>
|
||||
go(body)
|
||||
goBody(maxS + 1, maxA, maxF)(handler)
|
||||
case t.SEScopeExercise(body) =>
|
||||
go(body)
|
||||
|
||||
case _: t.SEDamlException | _: t.SEImportValue =>
|
||||
throw CompilationError(s"validate: unexpected $expr")
|
||||
}
|
||||
@tailrec
|
||||
def goLets(maxS: Int)(expr: t.SExpr): Unit = {
|
||||
def go = goBody(maxS, maxA, maxF)
|
||||
expr match {
|
||||
case t.SELet1General(rhs, body) =>
|
||||
go(rhs)
|
||||
goLets(maxS + 1)(body)
|
||||
case t.SELet1Builtin(_, args, body) =>
|
||||
args.foreach(go)
|
||||
goLets(maxS + 1)(body)
|
||||
case t.SELet1BuiltinArithmetic(_, args, body) =>
|
||||
args.foreach(go)
|
||||
goLets(maxS + 1)(body)
|
||||
case expr =>
|
||||
go(expr)
|
||||
}
|
||||
}
|
||||
go
|
||||
}
|
||||
goBody(0, 0, 0)(expr0)
|
||||
expr0
|
||||
}
|
||||
|
||||
@nowarn("msg=parameter value tokenPos in method compileFetchBody is never used")
|
||||
private[this] def compileFetchBody(env: Env, tmplId: Identifier, tmpl: Template)(
|
||||
cidPos: Position,
|
||||
|
@ -360,7 +360,14 @@ object SExpr {
|
||||
}
|
||||
|
||||
/** Case patterns */
|
||||
sealed trait SCasePat
|
||||
sealed trait SCasePat {
|
||||
|
||||
private[speedy] def numArgs: Int = this match {
|
||||
case _: SCPEnum | _: SCPPrimCon | SCPNil | SCPDefault | SCPNone => 0
|
||||
case _: SCPVariant | SCPSome => 1
|
||||
case SCPCons => 2
|
||||
}
|
||||
}
|
||||
|
||||
/** Match on a variant. On match the value is unboxed and pushed to stack. */
|
||||
final case class SCPVariant(id: Identifier, variant: Name, constructorRank: Int) extends SCasePat
|
||||
|
@ -18,8 +18,13 @@ package speedy
|
||||
*
|
||||
* 2: closure conversion
|
||||
* 3: transform to ANF
|
||||
* 4: validate the final expression which will run on the speedy machine
|
||||
*
|
||||
* Stage 1 is in Compiler.scala
|
||||
* Stage 2 is in ClosureConversion.scala
|
||||
* Stage 3 is in Anf.scala
|
||||
* Stage 4 is in ValidateCompilation.scala
|
||||
*
|
||||
* Stages 1 and 2 are in Compiler.scala; stage 3 in Anf.scala.
|
||||
* During Stage3 (ANF transformation), we move from this type (SExpr0) to SExpr,
|
||||
* and so have the expression form suitable for execution on a speedy machine.
|
||||
*
|
||||
|
@ -0,0 +1,119 @@
|
||||
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.lf.speedy
|
||||
|
||||
/** ValidateCompilation (Phase of the speedy compiler pipeline)
|
||||
*
|
||||
* This phases validates the final compilation result -- SExpr
|
||||
*/
|
||||
|
||||
import com.daml.lf.speedy.SValue._
|
||||
import com.daml.lf.speedy.{SExpr => t}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
private[lf] object ValidateCompilation {
|
||||
|
||||
case class CompilationError(error: String) extends RuntimeException(error, null, true, false)
|
||||
|
||||
private[speedy] def validateCompilation(expr0: t.SExpr): t.SExpr = {
|
||||
|
||||
def goV(v: SValue): Unit =
|
||||
v match {
|
||||
case _: SPrimLit | STNat(_) | STypeRep(_) =>
|
||||
case SList(a) => a.iterator.foreach(goV)
|
||||
case SOptional(x) => x.foreach(goV)
|
||||
case SMap(_, entries) =>
|
||||
entries.foreach { case (k, v) =>
|
||||
goV(k)
|
||||
goV(v)
|
||||
}
|
||||
case SRecord(_, _, args) => args.forEach(goV)
|
||||
case SVariant(_, _, _, value) => goV(value)
|
||||
case SEnum(_, _, _) => ()
|
||||
case SAny(_, v) => goV(v)
|
||||
case _: SPAP | SToken | SStruct(_, _) =>
|
||||
throw CompilationError("validate: unexpected s.SEValue")
|
||||
}
|
||||
|
||||
def goBody(maxS: Int, maxA: Int, maxF: Int): t.SExpr => Unit = {
|
||||
|
||||
def goLoc(loc: t.SELoc) = loc match {
|
||||
case t.SELocS(i) =>
|
||||
if (i < 1 || i > maxS)
|
||||
throw CompilationError(s"validate: SELocS: index $i out of range ($maxS..1)")
|
||||
case t.SELocA(i) =>
|
||||
if (i < 0 || i >= maxA)
|
||||
throw CompilationError(s"validate: SELocA: index $i out of range (0..$maxA-1)")
|
||||
case t.SELocF(i) =>
|
||||
if (i < 0 || i >= maxF)
|
||||
throw CompilationError(s"validate: SELocF: index $i out of range (0..$maxF-1)")
|
||||
}
|
||||
|
||||
def go(expr: t.SExpr): Unit = expr match {
|
||||
case loc: t.SELoc => goLoc(loc)
|
||||
case _: t.SEVal => ()
|
||||
case _: t.SEBuiltin => ()
|
||||
case _: t.SEBuiltinRecursiveDefinition => ()
|
||||
case t.SEValue(v) => goV(v)
|
||||
case t.SEAppAtomicGeneral(fun, args) =>
|
||||
go(fun)
|
||||
args.foreach(go)
|
||||
case t.SEAppAtomicSaturatedBuiltin(_, args) =>
|
||||
args.foreach(go)
|
||||
case t.SEAppGeneral(fun, args) =>
|
||||
go(fun)
|
||||
args.foreach(go)
|
||||
case t.SEAppAtomicFun(fun, args) =>
|
||||
go(fun)
|
||||
args.foreach(go)
|
||||
case t.SEMakeClo(fvs, n, body) =>
|
||||
fvs.foreach(goLoc)
|
||||
goBody(0, n, fvs.length)(body)
|
||||
case t.SECaseAtomic(scrut, alts) =>
|
||||
go(scrut)
|
||||
alts.foreach { case t.SCaseAlt(pat, body) =>
|
||||
val n = pat.numArgs
|
||||
goBody(maxS + n, maxA, maxF)(body)
|
||||
}
|
||||
case _: t.SELet1General => goLets(maxS)(expr)
|
||||
case _: t.SELet1Builtin => goLets(maxS)(expr)
|
||||
case _: t.SELet1BuiltinArithmetic => goLets(maxS)(expr)
|
||||
case t.SELocation(_, body) =>
|
||||
go(body)
|
||||
case t.SELabelClosure(_, expr) =>
|
||||
go(expr)
|
||||
case t.SETryCatch(body, handler) =>
|
||||
go(body)
|
||||
goBody(maxS + 1, maxA, maxF)(handler)
|
||||
case t.SEScopeExercise(body) =>
|
||||
go(body)
|
||||
|
||||
case _: t.SEDamlException | _: t.SEImportValue =>
|
||||
throw CompilationError(s"validate: unexpected $expr")
|
||||
}
|
||||
@tailrec
|
||||
def goLets(maxS: Int)(expr: t.SExpr): Unit = {
|
||||
def go = goBody(maxS, maxA, maxF)
|
||||
expr match {
|
||||
case t.SELet1General(rhs, body) =>
|
||||
go(rhs)
|
||||
goLets(maxS + 1)(body)
|
||||
case t.SELet1Builtin(_, args, body) =>
|
||||
args.foreach(go)
|
||||
goLets(maxS + 1)(body)
|
||||
case t.SELet1BuiltinArithmetic(_, args, body) =>
|
||||
args.foreach(go)
|
||||
goLets(maxS + 1)(body)
|
||||
case expr =>
|
||||
go(expr)
|
||||
}
|
||||
}
|
||||
go
|
||||
}
|
||||
goBody(0, 0, 0)(expr0)
|
||||
expr0
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user