From 4b33cd528a5952149ffb6db1bf271153cb9f7570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Sat, 5 Sep 2026 17:09:50 +0200 Subject: [PATCH] Drop unused pure calls --- .../src/test/scala/effekt/core/VMTests.scala | 46 +++++++++---------- .../effekt/core/optimizer/Deadcode.scala | 9 ++++ .../core/optimizer/StaticArguments.scala | 9 ++-- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/effekt/jvm/src/test/scala/effekt/core/VMTests.scala b/effekt/jvm/src/test/scala/effekt/core/VMTests.scala index 5ffb5417b7..238651e00a 100644 --- a/effekt/jvm/src/test/scala/effekt/core/VMTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/VMTests.scala @@ -382,13 +382,13 @@ class VMTests extends munit.FunSuite { val duality_of_compilation: Seq[(File, Option[Summary])] = Seq( examplesDir / "benchmarks" / "duality_of_compilation" / "erase_unused.effekt" -> Some(Summary( - staticDispatches = 21, + staticDispatches = 6, dynamicDispatches = 0, patternMatches = 0, - branches = 21, - pushedFrames = 6, - poppedFrames = 6, - allocations = 11, + branches = 6, + pushedFrames = 1, + poppedFrames = 1, + allocations = 0, closures = 0, variableReads = 0, variableWrites = 0, @@ -1089,14 +1089,14 @@ class VMTests extends munit.FunSuite { resumes = 0 )), examplesDir / "benchmarks" / "nofib" / "constraints.effekt" -> Some(Summary( - staticDispatches = 891409, - dynamicDispatches = 60355, - patternMatches = 1382034, - branches = 432810, - pushedFrames = 794941, - poppedFrames = 794941, - allocations = 852789, - closures = 10, + staticDispatches = 670319, + dynamicDispatches = 31053, + patternMatches = 1012640, + branches = 297136, + pushedFrames = 602935, + poppedFrames = 602935, + allocations = 642347, + closures = 6, variableReads = 0, variableWrites = 0, resets = 0, @@ -1149,16 +1149,16 @@ class VMTests extends munit.FunSuite { resumes = 0 )), examplesDir / "benchmarks" / "nofib" / "integer.effekt" -> Some(Summary( - staticDispatches = 4026, - dynamicDispatches = 792, - patternMatches = 2928, - branches = 1079, - pushedFrames = 3080, - poppedFrames = 3080, - allocations = 4532, - closures = 22, - variableReads = 924, - variableWrites = 792, + staticDispatches = 182, + dynamicDispatches = 0, + patternMatches = 135, + branches = 50, + pushedFrames = 104, + poppedFrames = 104, + allocations = 206, + closures = 0, + variableReads = 42, + variableWrites = 36, resets = 0, shifts = 0, resumes = 0 diff --git a/effekt/shared/src/main/scala/effekt/core/optimizer/Deadcode.scala b/effekt/shared/src/main/scala/effekt/core/optimizer/Deadcode.scala index e2d306fae5..6d019fa584 100644 --- a/effekt/shared/src/main/scala/effekt/core/optimizer/Deadcode.scala +++ b/effekt/shared/src/main/scala/effekt/core/optimizer/Deadcode.scala @@ -13,11 +13,20 @@ class Deadcode(reachable: Map[Id, Usage]) private def unused(id: Id): Boolean = !used(id) + /** A call that cannot be observed: the callee captures nothing, and neither does anything passed to it. */ + private def isPureCall(stmt: Stmt): Boolean = stmt match { + case Stmt.App(callee, _, _, bargs) => callee.capt.isEmpty && bargs.forall(_.capt.isEmpty) && stmt.tpe != Type.TBottom + case _ => false + } + override def rewrite(stmt: Stmt): Trampoline[Stmt] = stmt match { // Remove local unused definitions case Stmt.Def(id, block, body) if unused(id) => rewrite(body) case Stmt.Let(id, binding, body) if unused(id) => rewrite(body) + // Remove local unused 'val's as long as they are pure calls. + case Stmt.Val(id, binding, body) if unused(id) && isPureCall(binding) => rewrite(body) + case Stmt.Reset(body) => rewrite(body).map { case BlockLit(tparams, cparams, vparams, List(prompt), body) if unused(prompt.id) => body diff --git a/effekt/shared/src/main/scala/effekt/core/optimizer/StaticArguments.scala b/effekt/shared/src/main/scala/effekt/core/optimizer/StaticArguments.scala index bae4a51f1f..4b2a335fc3 100644 --- a/effekt/shared/src/main/scala/effekt/core/optimizer/StaticArguments.scala +++ b/effekt/shared/src/main/scala/effekt/core/optimizer/StaticArguments.scala @@ -155,17 +155,18 @@ object StaticArguments { case _ => false } } + lazy val read = body.free.freeIds // TODO: use Free.contains from #1453; reconsider free vs capt val isValueStatic = vparams.zipWithIndex.collect { - case (param, index) => vargs.nonEmpty && vargs.map(args => args(index)).forall { + case (param, index) => (vargs.nonEmpty && vargs.map(args => args(index)).forall { case ValueVar(other, _) => param.id == other case _ => false - } + }) || !read.contains(param.id) // vparam not read by body is vacuously static } val isBlockStatic = bparams.zipWithIndex.collect { - case (param, index) => bargs.nonEmpty && bargs.map(args => args(index)).forall { + case (param, index) => (bargs.nonEmpty && bargs.map(args => args(index)).forall { case BlockVar(other, _, _) => param.id == other case _ => false - } + }) || !read.contains(param.id) // bparam not read by body is vacuously static } id -> IsStatic(isTypeStatic, isValueStatic, isBlockStatic) }.toMap