Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exn #71

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open

Exn #71

Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c31c456
basics
Mar 13, 2022
263fed8
commit
Mar 14, 2022
c11d649
tmp
Mar 17, 2022
5269a3e
add exceptions task description
Mar 17, 2022
28f0c60
Update exceptions.md
dz333 Mar 17, 2022
30953ef
start exn typechecking. currently working on typeinference
Mar 28, 2022
86409a8
type inference
Apr 4, 2022
b0ca5cd
type inference
Apr 4, 2022
d3d7bfc
add typechecking based on the rules we discussed
yy665 Apr 19, 2022
7840a73
fix some ast
yy665 Apr 19, 2022
4f5a9a5
type checking working for exceptions? only positively
May 2, 2022
8c8f56b
untested, but done?
May 5, 2022
c60610e
added useful code for generating abort calls
dz333 May 5, 2022
2431f4d
added a simple abort-able lock
dz333 May 5, 2022
64a4d86
testing for exceptions
May 9, 2022
efe940a
Merge branch 'exn' of github.com:apl-cornell/PDL into exn
May 9, 2022
f13e86a
yeet old code
May 9, 2022
1a1f94c
exn_translation
yy665 Jun 22, 2022
9fcf978
add testcase
yy665 Jun 22, 2022
bd4d886
updated this example to reflect a bug - BaseTypeChecker should check …
dz333 Jun 22, 2022
16c8893
updated example to do a write
dz333 Jun 22, 2022
6f1b359
fixed some bugs in pretty printer, and made port checker pass run on …
dz333 Jun 23, 2022
f465d0d
partially resolved comments, enables exn_args and kill_rules
yy665 Jul 7, 2022
730997e
debugs and change kill rule impl
yy665 Jul 12, 2022
96a465b
added a spectable.clear() function that resets it to initial state
dz333 Jul 12, 2022
dc84843
added a clear method for our async mems that clears their request queues
dz333 Jul 12, 2022
68245bf
finish problem with old ModLock instantiations
dz333 Jul 12, 2022
7626eeb
tests passed
yy665 Jul 30, 2022
60c58bb
tests and fixes
yy665 Nov 29, 2022
7ac58af
Merge pull request #72 from yy665/exn
yy665 Nov 29, 2022
7fdfe43
resolve all comments and fix bugs, add ehr
yy665 Mar 1, 2023
d562687
Merge pull request #74 from yy665/exn
yy665 Mar 1, 2023
d9e204c
fix tests
yy665 Mar 1, 2023
3ea3e34
Merge pull request #75 from yy665/exn
yy665 Mar 1, 2023
b386fb4
rewrite impl with stgclear
yy665 Apr 6, 2023
635d92d
fix sq
yy665 Apr 6, 2023
24e6edd
fix
yy665 Apr 6, 2023
15231cd
Merge pull request #77 from yy665/exn
yy665 Apr 6, 2023
7935064
Update scala.yml
dz333 Apr 7, 2023
737bc6d
further exn change
yy665 Aug 8, 2023
ef2f70f
fix problem wrt iverilog version
yy665 Oct 11, 2023
5176c08
some fixes and testcases
yy665 Oct 16, 2023
6cc3a76
fix chkp + some ad hoc stuff
yy665 Oct 25, 2023
1b0f426
Merge pull request #78 from yy665/exn
yy665 Oct 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added examples/exn_output.pdl
Empty file.
6 changes: 4 additions & 2 deletions src/main/scala/pipedsl/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ object Main {
val specChecker = new SpeculationChecker(ctx)
specChecker.check(recvProg, None)
val lock_prog = LockOpTranslationPass.run(recvProg)
TimingTypeChecker.check(lock_prog, Some(basetypes))
val exnprog = ExnTranslationPass.run(lock_prog)
TimingTypeChecker.check(lock_prog, Some(basetypes))
val exnTranslationPass = new ExnTranslationPass()
val exnprog = exnTranslationPass.run(lock_prog)
new PrettyPrinter(None).printProgram(exnprog)
yy665 marked this conversation as resolved.
Show resolved Hide resolved
if (printOutput) {
val writer = new PrintWriter(outputFile)
writer.write("Passed")
Expand Down
13 changes: 7 additions & 6 deletions src/main/scala/pipedsl/codegen/bsv/BSVPrettyPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ object BSVPrettyPrinter {
case BTruncate(e) => mkExprString("truncate(", toBSVExprStr(e), ")")
case BStructAccess(rec, field) => toBSVExprStr(rec) + "." + toBSVExprStr(field)
case BVar(name, _) => name
case BBOp(op, lhs, rhs, isInfix) if isInfix => mkExprString("(", toBSVExprStr(lhs), op, toBSVExprStr(rhs), ")")
case BBOp(op, lhs, rhs, isInfix) if !isInfix => mkExprString( op + "(", toBSVExprStr(lhs), ",", toBSVExprStr(rhs), ")")
case BBOp(op, lhs, rhs, isInfix, omitBrackets) if isInfix && !omitBrackets => mkExprString("(", toBSVExprStr(lhs), op, toBSVExprStr(rhs), ")")
case BBOp(op, lhs, rhs, isInfix, omitBrackets) if isInfix && omitBrackets => mkExprString(toBSVExprStr(lhs), op, toBSVExprStr(rhs))
case BBOp(op, lhs, rhs, isInfix, _) if !isInfix => mkExprString( op + "(", toBSVExprStr(lhs), ",", toBSVExprStr(rhs), ")")
case BUOp(op, expr) => mkExprString("(", op, toBSVExprStr(expr), ")")
//TODO incorporate bit types into the typesystem properly
//and then remove the custom pack/unpack operations
Expand Down Expand Up @@ -238,10 +239,10 @@ object BSVPrettyPrinter {
}

def printBSVRule(rule: BRuleDef): Unit = {
val condString = if (rule.conds.nonEmpty) {
"(" + rule.conds.map(c => toBSVExprStr(c)).mkString(" && ") + ")"
} else {
""
val condString = rule.conds match {
case BDontCare => ""
case _ => toBSVExprStr(rule.conds)

}
w.write(mkStatementString("rule", rule.name, condString))
incIndent()
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/pipedsl/codegen/bsv/BSVSyntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ object BSVSyntax {
case class BStructLit(typ: BStruct, fields: Map[BVar, BExpr]) extends BExpr
case class BStructAccess(rec: BExpr, field: BExpr) extends BExpr
case class BVar(name: String, typ: BSVType) extends BExpr
case class BBOp(op: String, lhs: BExpr, rhs: BExpr, isInfix: Boolean = true) extends BExpr
case class BBOp(op: String, lhs: BExpr, rhs: BExpr, isInfix: Boolean = true, omitBrackets: Boolean = false) extends BExpr
case class BUOp(op: String, expr: BExpr) extends BExpr
case class BBitExtract(expr: BExpr, start: BIndex, end: BIndex) extends BExpr
case class BConcat(first: BExpr, rest: List[BExpr]) extends BExpr
Expand Down Expand Up @@ -502,7 +502,7 @@ object BSVSyntax {

case class BStructDef(typ: BStruct, derives: List[String])

case class BRuleDef(name: String, conds: List[BExpr], body: List[BStatement])
case class BRuleDef(name: String, conds: BExpr, body: List[BStatement])
yy665 marked this conversation as resolved.
Show resolved Hide resolved

case class BMethodSig(name: String, typ: MethodType, params: List[BVar])

Expand Down
180 changes: 90 additions & 90 deletions src/main/scala/pipedsl/codegen/bsv/BluespecGeneration.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package pipedsl.codegen.bsv

import BSVSyntax.{BBOp, _}
import BSVSyntax.{BBOp, BEmpty, _}
import pipedsl.common.DAGSyntax.{PStage, PipelineEdge}
import pipedsl.common.Errors.{UnexpectedCommand, UnexpectedExpr}
import pipedsl.common.LockImplementation.{LockInterface, MethodInfo}
Expand Down Expand Up @@ -398,7 +398,7 @@ object BluespecGeneration {
private val outputData = BVar("data", translator.toType(mod.ret.getOrElse(TVoid())))
private val outputQueue = BVar("outputQueue", bsInts.getOutputQType(threadIdVar.typ, outputData.typ))
//Registers for exceptions
private val globalExnFlag = BVar("globalExnFlag", bsInts.getRegType(BBool))
private val globalExnFlag = BVar("_globalExnFlag", bsInts.getRegType(BBool))
//Data types for passing between stages
private val edgeStructInfo = getEdgeStructInfo(otherStages, addTId = true, addSpecId = mod.maybeSpec)
//First stage should have exactly one input edge by definition
Expand Down Expand Up @@ -432,7 +432,7 @@ object BluespecGeneration {
vars + (m.name -> nvar)
case _ => vars
}
}) + (mod.name -> BVar("_lock_" + mod.name, translator.getLockedModType(LockImplementation.getModLockImpl)))
})

private val lockRegions: LockInfo = mod.modules.foldLeft[LockInfo](Map())((locks, m) => {
locks + (m.name -> BVar(genLockRegionName(m.name),
Expand Down Expand Up @@ -550,6 +550,19 @@ object BluespecGeneration {
}

private var stgSpecOrder: Int = 0

private def getMergedCond(lhs: BExpr, rhs: BExpr): BExpr = {
if (lhs == BDontCare && rhs == BDontCare){
BDontCare
} else if (lhs == BDontCare){
rhs
} else if (rhs == BDontCare){
lhs
} else {
BBOp("&&", lhs, rhs, isInfix = true, omitBrackets = true)
}
}

/**
* Given a pipeline stage and the necessary edge info,
* generate a BSV module definition.
Expand All @@ -570,7 +583,7 @@ object BluespecGeneration {
val execRule = getStageRule(stg)
//Add a stage kill rule if it needs one
val killRule = getStageKillRule(stg)
val rules = if (mod.maybeSpec && killRule.isDefined) List(execRule, killRule.get) else List(execRule)
val rules = if ((mod.maybeSpec && killRule.isDefined) || (is_excepting(mod) && killRule.isDefined)) List(execRule, killRule.get) else List(execRule)
stgSpecOrder = 0
translator.setVariablePrefix("")
(sBody, rules)
Expand All @@ -595,7 +608,7 @@ object BluespecGeneration {
BDisplay(Some(mod.name.v + ":Thread %d:Executing Stage " + stg.name + " %t"),
List(translator.toBSVVar(threadIdVar), BTime))
} else BEmpty
BRuleDef( genParamName(stg) + "_execute", blockingConds ++ recvConds,
BRuleDef( genParamName(stg) + "_execute", getMergedCond(blockingConds, recvConds),
writeCmdDecls ++ writeCmdStmts ++ queueStmts :+ debugStmt)
}

Expand All @@ -608,46 +621,40 @@ object BluespecGeneration {
*/
private def getStageKillRule(stg: PStage): Option[BRuleDef] = {
val killConds = getKillConds(stg.getCmds)
if (killConds.isEmpty) {
None
} else {
val recvConds = getRecvConds(stg.getCmds)
val debugStmt = if (debug) {
BDisplay(Some(mod.name.v + ":SpecId %d: Killing Stage " + stg.name + "%t"),
List(getSpecIdVal, BTime))
} else { BEmpty }
val deqStmts = getEdgeQueueStmts(stg, stg.inEdges) ++ getRecvCmds(stg.getCmds)
val freeStmt = BExprStmt(bsInts.getSpecFree(specTable, getSpecIdVal))
Some(BRuleDef( genParamName(stg) + "_kill", killConds ++ recvConds, deqStmts :+ freeStmt :+ debugStmt))
}
val recvConds = getRecvConds(stg.getCmds)
val debugStmt = if (debug) {
BDisplay(Some(mod.name.v + ":SpecId %d: Killing Stage " + stg.name + "%t"),
List(getSpecIdVal, BTime))
} else { BEmpty }
val deqStmts = getEdgeQueueStmts(stg, stg.inEdges) ++ getRecvCmds(stg.getCmds)
val freeStmt = BExprStmt(bsInts.getSpecFree(specTable, getSpecIdVal))
Some(BRuleDef( genParamName(stg) + "_kill", getMergedCond(killConds, recvConds), deqStmts :+ freeStmt :+ debugStmt))
}

private def getKillConds(cmds: Iterable[Command]): List[BExpr] = {
cmds.foldLeft(List[BExpr]())((l, c) => c match {
//check definitely misspeculated
// isValid(spec) && !fromMaybe(True, check(spec))
private def getKillConds(cmds: Iterable[Command]): BExpr = {
var resultingConds: BExpr = BDontCare
var readGlobalExnFlag: Boolean = false
cmds.foreach(c => c match {
case CCheckSpec(_) =>
l :+ BBOp("&&", BIsValid(translator.toBSVVar(specIdVar)),
resultingConds = getMergedCond(resultingConds, BBOp("&&", BIsValid(translator.toBSVVar(specIdVar)),
//order is LATE if stage has no update
BUOp("!", BFromMaybe(BBoolLit(true),
bsInts.getSpecCheck(specTable, getSpecIdVal, stgSpecOrder))))
bsInts.getSpecCheck(specTable, getSpecIdVal, stgSpecOrder)))))
//also need these in case we're waiting on responses we need to dequeue
case IStageClear() =>
yy665 marked this conversation as resolved.
Show resolved Hide resolved
l :+ globalExnFlag

readGlobalExnFlag = true
case ICondCommand(cond, cs) =>
val condconds = getKillConds(cs)
if (condconds.nonEmpty) {
val nestedConds = condconds.tail.foldLeft(condconds.head)((exp, n) => {
BBOp("&&", exp, n)
})
val newCond = BBOp("||", BUOp("!", translator.toExpr(cond)), nestedConds)
l :+ newCond
} else {
l
}
case _ => l
val nestedConds = getKillConds(cs)
val newCond = BBOp("||", BUOp("!", translator.toExpr(cond)), nestedConds)
resultingConds = getMergedCond(resultingConds, newCond)
case _ => BDontCare
})

if(readGlobalExnFlag){
resultingConds = BBOp("||", globalExnFlag, resultingConds)
}

resultingConds
}

private def translateMethod(mod: BVar, mi: MethodInfo): BMethodInvoke = {
Expand All @@ -670,115 +677,107 @@ object BluespecGeneration {
* @param cmds The list of commands to translate
* @return The list of translated blocking commands
*/
private def getBlockingConds(cmds: Iterable[Command]): List[BExpr] = {
cmds.foldLeft(List[BExpr]())((l, c) => c match {
private def getBlockingConds(cmds: Iterable[Command]): BExpr = {
var resultingConds: BExpr = BDontCare
cmds.foreach(c => c match {
case CLockStart(mod) =>
l :+ bsInts.getCheckStart(lockRegions(mod))
resultingConds = getMergedCond(resultingConds, bsInts.getCheckStart(lockRegions(mod)))
case cl@IReserveLock(_, mem) =>
val methodInfo = LockImplementation.getCanReserveInfo(cl)
if (methodInfo.isDefined) {
l :+ translateMethod(modParams(mem.id), methodInfo.get)
resultingConds = getMergedCond(resultingConds, translateMethod(modParams(mem.id), methodInfo.get))
} else {
l
resultingConds
}
case cl@ICheckLockOwned(mem, _, _) =>
val methodInfo = LockImplementation.getBlockInfo(cl)
if (methodInfo.isDefined) {
l :+ translateMethod(getLockName(mem.id), methodInfo.get)
resultingConds = getMergedCond(resultingConds, translateMethod(getLockName(mem.id), methodInfo.get))
} else {
l
resultingConds
}
case im@IMemWrite(mem, addr, data, _, _, isAtomic) if isAtomic =>
val methodInfo = LockImplementation.getCanAtomicWrite(mem, addr, data, im.portNum)
if (methodInfo.isDefined) {
l :+ translateMethod(getLockName(mem), methodInfo.get)
resultingConds = getMergedCond(resultingConds, translateMethod(getLockName(mem), methodInfo.get))
} else {
l
resultingConds
}
case im@IMemSend(_, _, mem, data, addr, _, _, isAtomic) if isAtomic =>
val methodInfo = LockImplementation.getCanAtomicAccess(mem, addr, data, im.portNum)
if (methodInfo.isDefined) {
l :+ translateMethod(getLockName(mem), methodInfo.get)
resultingConds = getMergedCond(resultingConds, translateMethod(getLockName(mem), methodInfo.get))
} else {
l
resultingConds
}
//these are just to find EMemAccesses that are also atomic
case CAssign(_, rhs) => l ++ getBlockConds(rhs)
case CRecv(_, rhs) => l ++ getBlockConds(rhs)
case COutput(_) => l :+ bsInts.getOutCanWrite(outputQueue, translator.toBSVVar(threadIdVar))
case CAssign(_, rhs) => resultingConds = getMergedCond(resultingConds, getBlockConds(rhs))
case CRecv(_, rhs) => resultingConds = getMergedCond(resultingConds, getBlockConds(rhs))
case COutput(_) => resultingConds = getMergedCond(resultingConds, bsInts.getOutCanWrite(outputQueue, translator.toBSVVar(threadIdVar)))
//Execute ONLY if check(specid) == Valid(True) && isValid(specid)
// fromMaybe(False, check(specId)) <=> check(specid) == Valid(True)
case CCheckSpec(isBlocking) if isBlocking => l ++ List(
case CCheckSpec(isBlocking) if isBlocking => resultingConds = getMergedCond(resultingConds,
BBOp("||", BUOp("!", BIsValid(translator.toBSVVar(specIdVar))),
BFromMaybe(BBoolLit(false), bsInts.getSpecCheck(specTable, getSpecIdVal, stgSpecOrder))
)
)
//Execute if check(specid) != Valid(False)
//fromMaybe(True, check(specId)) <=> check(specid) == (Valid(True) || Invalid)
case CCheckSpec(isBlocking) if !isBlocking => l ++ List(
case CCheckSpec(isBlocking) if !isBlocking => resultingConds = getMergedCond(resultingConds,
BBOp("||", BUOp("!", BIsValid(translator.toBSVVar(specIdVar))),
//order is LATE if stage has no update
BFromMaybe(BBoolLit(true), bsInts.getSpecCheck(specTable, getSpecIdVal, stgSpecOrder))
)
)
case ICondCommand(cond, cs) =>
val condconds = getBlockingConds(cs)
if (condconds.nonEmpty) {
val nestedConds = condconds.tail.foldLeft(condconds.head)((exp, n) => {
BBOp("&&", exp, n)
})
val newCond = BBOp("||", BUOp("!", translator.toExpr(cond)), nestedConds)
l :+ newCond
} else {
l
}
case _ => l
val nestedConds = getMergedCond(getBlockingConds(cs), BDontCare)
val newCond = BBOp("||", BUOp("!", translator.toExpr(cond)), nestedConds)
resultingConds = getMergedCond(resultingConds, newCond)
case _ => resultingConds
})
resultingConds
}

//only necessary to find atomic Reads and get their blocking conds
private def getBlockConds(e: Expr): List[BExpr] = e match {
private def getBlockConds(e: Expr): BExpr = e match {
case EIsValid(ex) => getBlockConds(ex)
case EFromMaybe(ex) => getBlockConds(ex)
case EToMaybe(ex) => getBlockConds(ex)
case EUop(_, ex) => getBlockConds(ex)
case EBinop(_, e1, e2) => getBlockConds(e1) ++ getBlockConds(e2)
case EBinop(_, e1, e2) => getMergedCond(getBlockConds(e1), getBlockConds(e2))
case em@EMemAccess(mem, index, _, _, _, isAtomic) if isAtomic =>
val methodInfo = LockImplementation.getCanAtomicRead(mem, index, em.portNum)
if (methodInfo.isDefined) {
List(translateMethod(getLockName(mem), methodInfo.get))
} else List()
translateMethod(getLockName(mem), methodInfo.get)
} else BDontCare
case EBitExtract(num, _, _) => getBlockConds(num)
//can't appear in cond of ternary
case ETernary(_, tval, fval) => getBlockConds(tval) ++ getBlockConds(fval)
case EApp(_, args) => args.foldLeft(List[BExpr]())((l, a) => {
l ++ getBlockConds(a)
})
case ECall(_, _, args, isAtomic) => args.foldLeft(List[BExpr]())((l, a) => {
l ++ getBlockConds(a)
})
case ETernary(_, tval, fval) => getMergedCond(getBlockConds(tval), getBlockConds(fval))
case EApp(_, args) =>
var resultingConds: BExpr = BDontCare
args.foreach(a => getMergedCond(resultingConds, getBlockConds(a)))
resultingConds
case ECall(_, _, args, isAtomic) =>
var resultingConds: BExpr = BDontCare
args.foreach(a => getMergedCond(resultingConds, getBlockConds(a)))
resultingConds
case ECast(_, exp) => getBlockConds(exp)
case _ => List()
case _ => BDontCare
}
private def getRecvConds(cmds: Iterable[Command]): List[BExpr] = {
cmds.foldLeft(List[BExpr]())((l, c) => c match {
private def getRecvConds(cmds: Iterable[Command]): BExpr = {
var resultingConds: BExpr = BDontCare
cmds.foreach(c => c match {
case IMemRecv(mem: Id, handle: EVar, _: Option[EVar]) =>
l :+ bsInts.getCheckMemResp(modParams(mem), translator.toVar(handle), c.portNum, isLockedMemory(mem))
resultingConds = getMergedCond(resultingConds, bsInts.getCheckMemResp(modParams(mem), translator.toVar(handle), c.portNum, isLockedMemory(mem)))
case IRecv(handle, sender, _) =>
l :+ bsInts.getModCheckHandle(modParams(sender), translator.toExpr(handle))
resultingConds = getMergedCond(resultingConds, bsInts.getModCheckHandle(modParams(sender), translator.toExpr(handle)))
case ICondCommand(cond, cs) =>
val condconds = getRecvConds(cs)
if (condconds.nonEmpty) {
val nestedConds = condconds.tail.foldLeft(condconds.head)((exp, n) => {
BBOp("&&", exp, n)
})
val newCond = BBOp("||", BUOp("!", translator.toExpr(cond)), nestedConds)
l :+ newCond
} else {
l
}
case _ => l
val nestedConds = getMergedCond(getRecvConds(cs), BDontCare)
val newCond = BBOp("||", BUOp("!", translator.toExpr(cond)), nestedConds)
resultingConds = getMergedCond(resultingConds, newCond)
case _ => resultingConds
})
resultingConds
}

/**
Expand Down Expand Up @@ -1375,6 +1374,7 @@ object BluespecGeneration {
case CAssign(_, _) => None
case CExpr(_) => None
case CEmpty() => None
case ISetGlobalExnFlag(state) => Some(BModAssign(globalExnFlag, BBoolLit(state)))
case _: IStageClear => None
case _: InternalCommand => throw UnexpectedCommand(cmd)
case CRecv(_, _) => throw UnexpectedCommand(cmd)
Expand Down
10 changes: 5 additions & 5 deletions src/main/scala/pipedsl/codegen/bsv/BluespecInterfaces.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,21 @@ class BluespecInterfaces() {
val debugStart = if (debug) { BDisplay(Some("Starting Pipeline %t"), List(BTime)) } else BEmpty
val initRule = BRuleDef(
name = "initTB",
conds = List(initCond),
conds = initCond,
body = initStmts :+ setStartReg :+ debugStart
)
val timerRule = BRuleDef(
name = "timerCount",
conds = List(),
conds = BDontCare,
body = List(BModAssign(timerReg, BBOp("+", timerReg, BOne)))
)
val timerDone = BBOp(">=", timerReg, BIntLit(1000000,10,32))
val doneConds = if (modDone.isEmpty) {
List(timerDone)
timerDone
} else {
List(BBOp("||", timerDone, modDone.reduce((l, r) => {
BBOp("||", timerDone, modDone.reduce((l, r) => {
BBOp("&&", l, r)}
)))
))
}
val doneRule = BRuleDef(
name = "stopTB",
Expand Down
Loading