Move closureConvert and validate compiler phases into separate files. (#11656)

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
nickchapman-da 2021-11-11 13:38:05 +00:00 committed by GitHub
parent 87f282c7f3
commit 7296ba4dfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 334 additions and 280 deletions

View File

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

View File

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

View File

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

View File

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

View File

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