Skip to content

Commit 1b54544

Browse files
committed
Find last usage of variables
1 parent 4f78031 commit 1b54544

File tree

7 files changed

+156
-26
lines changed

7 files changed

+156
-26
lines changed

src/main/scala/fred/AST.scala

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ case class MatchArm(pat: MatchPattern, body: Expr, span: Span)
2626
*/
2727
case class MatchPattern(
2828
ctorName: Spanned[String],
29+
/** (fieldName, varName) tuples */
2930
bindings: List[(Spanned[String], Spanned[String])]
3031
)
3132

src/main/scala/fred/Translator.scala

+144-15
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,11 @@ object Translator {
385385

386386
val mangledVars = mutable.Map.empty[VarDef, String]
387387

388+
val lastUsagesPost = mutable.Map.empty[Expr, Set[VarDef]]
389+
val lastUsagesPre = mutable.Map.empty[Expr, Set[VarDef]]
390+
391+
val unusedVars = mutable.Set.empty[VarDef]
392+
388393
private var resVarCounter = 0
389394

390395
def translateType(typ: TypeDef): String = {
@@ -437,18 +442,33 @@ object Translator {
437442

438443
/** Returns code for the function's declaration and implementation */
439444
def fnToC(fn: FnDef)(using bindings: Bindings): (String, String) = {
445+
given Bindings = bindings.enterFn(fn)
446+
lastUsagesPre.clear()
447+
lastUsagesPost.clear()
448+
unusedVars.clear()
449+
450+
val paramDefs = fn.params
451+
.map(param => VarDef.Param(param, bindings.getType(param.typ))).toSet
452+
val unusedParams = findLastUsages(fn.body, paramDefs).map(_.name)
453+
assert(
454+
unusedParams.subsetOf(paramDefs.map(_.name)),
455+
s"Function: ${fn.name.value}, unused vars: $unusedParams"
456+
)
457+
458+
// println(lastUsagesPre.view.mapValues(_.map(_.name)).toMap)
459+
println(fn.name.value)
460+
println(lastUsagesPost.view.mapValues(_.map(_.name)).toMap)
461+
440462
// param names don't need to be mangled because they're the first occurrence
441463
val params = fn.params
442464
.map(param => s"${typeRefToC(param.typ.name)} ${param.name.value}")
443465
.mkString(", ")
444-
val paramsSetup = fn.params
445-
.map(param => incrRc(param.name.value, bindings.getType(param.typ)))
446-
.mkString("\n")
447-
val paramsTeardown = fn.params
448-
.map(param => decrRc(param.name.value, bindings.getType(param.typ)))
449-
.mkString("\n")
450-
val (bodySetup, body, bodyTeardown) =
451-
exprToC(fn.body)(using bindings.enterFn(fn))
466+
val paramsSetup = fn.params.map { param =>
467+
if (unusedParams(param.name.value)) ""
468+
else incrRc(param.name.value, bindings.getType(param.typ))
469+
}.mkString("\n")
470+
471+
val (bodySetup, body, bodyTeardown) = exprToC(fn.body)
452472
val resVar = newVar("ret")
453473
val typeToC = typeRefToC(fn.returnType.name)
454474

@@ -474,13 +494,99 @@ object Translator {
474494
|${indent(1)(bodySetup)}
475495
| $typeToC $resVar = $body;
476496
|${indent(1)(bodyTeardown)}
477-
|${indent(1)(paramsTeardown)}
478497
|$triggerGC
479498
| return $resVar;
480499
|}""".stripMargin
481500
(decl, impl)
482501
}
483502

503+
/** Go through an expression backwards, finding the last usage of every
504+
* variable. These last usages are added to `lastUsagesPre` and
505+
* `lastUsagesPost`.
506+
*
507+
* @param vars
508+
* Variables that so far have been unused ("so far" while moving
509+
* backwards, not forwards)
510+
* @return
511+
* The remaining variables that weren't used in this expression
512+
*/
513+
private def findLastUsages(expr: Expr, vars: Set[VarDef])(using
514+
bindings: Bindings
515+
): Set[VarDef] = {
516+
expr match {
517+
case IntLiteral(_, _) | StringLiteral(_, _) => vars
518+
case VarRef(name, span) =>
519+
val varDef = bindings.vars(name)
520+
if (vars.contains(varDef)) {
521+
lastUsagesPost(expr) = Set(varDef)
522+
vars - varDef
523+
} else { vars }
524+
case SetFieldExpr(lhsObj, lhsField, value, span) =>
525+
val varDef = bindings.vars(lhsObj.value)
526+
if (vars.contains(varDef)) {
527+
lastUsagesPost(expr) = Set(varDef)
528+
findLastUsages(value, vars - varDef)
529+
} else { findLastUsages(value, vars) }
530+
case FieldAccess(obj, field, typ) => findLastUsages(obj, vars)
531+
case BinExpr(lhs, op, rhs, typ) =>
532+
val remVars = findLastUsages(rhs, vars)
533+
findLastUsages(lhs, remVars)
534+
case FnCall(fnName, args, resolvedFn, typ, span) => args
535+
.foldRight(vars)(findLastUsages(_, _))
536+
case CtorCall(ctorName, values, span) => values
537+
.foldRight(vars) { case ((_, expr), vars) =>
538+
findLastUsages(expr, vars)
539+
}
540+
case expr @ LetExpr(name, value, body, span) =>
541+
val varType = typer.types(value)
542+
val varDef = VarDef.Let(expr, varType)
543+
val newBindings = bindings.withVar(name.value, varDef)
544+
var bodyRemVars =
545+
findLastUsages(body, vars + varDef)(using newBindings)
546+
if (bodyRemVars.contains(varDef)) {
547+
unusedVars += varDef
548+
bodyRemVars -= varDef
549+
}
550+
findLastUsages(value, bodyRemVars)
551+
case IfExpr(cond, thenBody, elseBody, span) =>
552+
val thenRemVars = findLastUsages(thenBody, vars)
553+
val elseRemVars = findLastUsages(elseBody, vars)
554+
lastUsagesPre(thenBody) = thenRemVars -- elseRemVars
555+
lastUsagesPre(elseBody) = elseRemVars -- thenRemVars
556+
findLastUsages(cond, thenRemVars & elseRemVars)
557+
case matchExpr @ MatchExpr(obj, arms, armsSpan) =>
558+
val objType = typer.types(obj).asInstanceOf[TypeDef]
559+
560+
val armVars = arms.map {
561+
case arm @ MatchArm(
562+
pat @ MatchPattern(ctorName, patBindings),
563+
body,
564+
_
565+
) =>
566+
val newBindings = bindings.enterPattern(matchExpr, pat, objType)
567+
val newVars = patBindings.map { case (_, varName) =>
568+
newBindings.vars(varName.value)
569+
}.toSet
570+
val remVars =
571+
findLastUsages(body, vars | newVars)(using newBindings)
572+
573+
unusedVars ++= newVars -- remVars
574+
575+
arm -> remVars
576+
}.toMap
577+
578+
for (arm <- arms) {
579+
val remVars = armVars(arm)
580+
val otherArms = arms.filter(_ != arm)
581+
val otherVars = otherArms.map(armVars).reduce(_ | _)
582+
lastUsagesPre(arm.body) = remVars -- otherVars
583+
}
584+
585+
val commonUnused = armVars.values.reduce(_ | _)
586+
findLastUsages(obj, commonUnused)
587+
}
588+
}
589+
484590
/** @return
485591
* A tuple containing the C code for setup, the C code for the actual
486592
* expression, and the C code for teardown (decrementing reference
@@ -491,7 +597,7 @@ object Translator {
491597
private def exprToC(
492598
expr: Expr
493599
)(using bindings: Bindings): (String, String, String) = {
494-
expr match {
600+
val (setup, toC, teardown) = expr match {
495601
case IntLiteral(value, _) => ("", value.toString, "")
496602
case StringLiteral(value, _) =>
497603
("", s"\"${value.replace("\"", "\\\"")}\"", "")
@@ -543,8 +649,8 @@ object Translator {
543649
val (valueSetup, valueToC, valueTeardown) = exprToC(value)
544650
val typ = typer.types(value)
545651
val shouldMangle = bindings.vars.contains(name.value)
546-
val newBindings = bindings
547-
.withVar(name.value, VarDef.Let(letExpr, typ))
652+
val varDef = VarDef.Let(letExpr, typ)
653+
val newBindings = bindings.withVar(name.value, varDef)
548654
val mangledName =
549655
if (shouldMangle) newMangledVar(name.value) else name.value
550656
if (shouldMangle) {
@@ -554,8 +660,10 @@ object Translator {
554660
val (bodySetup, bodyToC, bodyTeardown) =
555661
exprToC(body)(using newBindings)
556662

663+
val decr =
664+
if (unusedVars.contains(varDef)) decrRc(mangledName, typ) else ""
557665
(
558-
s"$valueSetup\n$letSetup\n$valueTeardown\n$bodySetup",
666+
s"$valueSetup\n$letSetup\n$valueTeardown\n$decr\n$bodySetup",
559667
bodyToC,
560668
s"$bodyTeardown\n$letTeardown"
561669
)
@@ -615,6 +723,7 @@ object Translator {
615723

616724
val armsToC = arms.map {
617725
case MatchArm(pat @ MatchPattern(ctorName, patBindings), body, _) =>
726+
// TODO decrement unused variables immediately
618727
val variant = objType.cases.find(_.name.value == ctorName.value)
619728
.get
620729
val oldBindings = bindings
@@ -665,6 +774,27 @@ object Translator {
665774

666775
(setup, resVar, teardown)
667776
}
777+
778+
val setupDecrs = lastUsagesPre.getOrElse(expr, Set.empty).map { varDef =>
779+
decrRc(mangledVars.getOrElse(varDef, varDef.name), varDef.typ)
780+
}.mkString("", "\n", "\n")
781+
val teardownDecrs = lastUsagesPost.getOrElse(expr, Set.empty)
782+
.map { varDef =>
783+
decrRc(mangledVars.getOrElse(varDef, varDef.name), varDef.typ)
784+
}.mkString("\n", "\n", "")
785+
786+
if (lastUsagesPre.getOrElse(expr, Set.empty).nonEmpty) {
787+
println(
788+
s"last usage pre: ${lastUsagesPre(expr).map(_.name)}, expr: $expr"
789+
)
790+
}
791+
if (lastUsagesPost.getOrElse(expr, Set.empty).nonEmpty) {
792+
println(
793+
s"last usage post: ${lastUsagesPost(expr).map(_.name)}, expr: $expr"
794+
)
795+
}
796+
797+
(setupDecrs + setup, toC, teardown + teardownDecrs)
668798
}
669799

670800
private def newMangledVar(baseName: String): String = {
@@ -678,8 +808,7 @@ object Translator {
678808
): (String, String) = {
679809
val setup = s"""|${typeRefToC(typ.name)} ${varName} = $value;
680810
|${incrRc(varName, typ)}""".stripMargin
681-
val teardown = decrRc(varName, typ)
682-
(setup, teardown)
811+
(setup, "")
683812
}
684813

685814
/** Create a variable name that hopefully won't conflict with any other

src/test/resources/snapshot/exec/basic-cycle.c

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/test/resources/snapshot/exec/basic-main.c

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/test/resources/snapshot/exec/bucket-empty-recreate-3jilws.c

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/test/resources/snapshot/exec/contrived-needs-sorting.c

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/test/resources/snapshot/exec/lazy-mark-scan-83wesh.c

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)