Skip to content

Conversation

@henrybatt
Copy link

Adds a 2-phase program slicer into BASIL to reduce the IR based on a given criterion.

Introduces two new flags outlining where to begin slicing from and what the initial criterion variables are:

--slice <str> // The name of the starting block to slice from.
--criterion <list<str>> // List of comma separated variable names making up criterion.

Uses 2 phases to reduce the provided program:

  1. Phase 1 - Criteria Generation: Transforms the initial criterion to generate the criterion state for each statement using the IDE Solver.
  2. Phase 2 - IR Reduction: Removes irrelevant program statements and procedure parameters that don't impact the criterion values.

Adds a collection of slicer tests to ensure its expected functionality across different scenarios, ranging from:

  • Single/Multiple calls.
  • Local/Global variables.
  • Different types of criterion impacts
  • Various parameter reduction results.
    Additionally, some basic tests were created that run a subset of the SystemTests with the slicer to ensure that the outputted Boogie file still verifies.

Additional Changes:

  • Replaces empty goto statements with assume false in the Boogie translation phase instead of generating invalid empty goto.
  • Added TokenRead to convert flag args from comma-separated values into a list of strings.
  • Added support to define IDE transfer functions without the entire IDE Solver + program.
  • Fixed DSL directCall(String, Option[String]) to actually add label into the created DirectCall statement.
  • Fixed BASILTest checkVerify to accurately return iff Boogie successfully verified instead of always returning false.

Better grouping of the different commands in the match.
Allows for the definition of IDE Analysis Edges without a Program so
transfer functions can be reused in Objects
No longer needs callback eval instead gets pre/post separately.
Phase2 of slicer completed
Based off Simp code but uses results already computed instead of doing a
new live vars analysis.
Some statements weren't registered as having an impact if the variable
was replace by an already existing one
--slice <blockName>
--criterion list<variable_names>
does not parse the flags into valid criterion yet
Old SC used to be applied after a statement ran, but now it will be
applied for the given statement. This makes it closer to usage in
literature
henrybatt added 28 commits May 23, 2025 14:12
Converts index expressions of literals into Registers during analysis.
Analyses called procedures to determine if these new index are also
modified
Instead let the slicer strip away these methods. Maybe what the
criterion is in is now unreachable. We can still verify it
Copy link
Contributor

@ailrst ailrst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work, some comments in the code

I'd like to know how much it depends on the SSA+regions structure or if its semantically correct for any IR 'dialect'. Ideally I'd like the latter with just implicit precision gains from the earlier transforms. Obviously no need for major changes, I just want to get an idea of how strong the assumptions are.

Its good to have substantial tests including loops and everything.

Autoformater can be run ./mill scalafmt.reformat

case i: IndirectCall => Set(i.target)
case r: Return => r.outParams.keys.toSet[Variable] ++ r.outParams.values.flatMap(_.variables)
case s: (NOP | GoTo | Unreachable) => Set()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-implements ir.allVarsPos() in IRCursor.scala

case None => throw IllegalArgumentException("Slicer requires --dsa checks|standard")
case Some(Prereq) => throw IllegalArgumentException("Slicer requires --dsa checks|standard")
case _ => ()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, slicer doesn't directly use the results from DSA but implicitly benefits from the region separation, why is the region pass enforced here?

try {
val targetedBlock = program.labelToBlock(slicerConfig.blockLabel)

val visited = mutable.Map[String, Boolean]().withDefaultValue(false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a set where membership means visited.

None
}

} catch {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This try catch shouldn't need to enclose the whole function? It looks like only the first statement can throw, so its probably better to just check slicerConfig.blockLabel exists and return early. Scala return is allows if its not nested in a deeper closure/lambda.

class Phase1 {
protected var nop: Option[NOP] = None

protected def insertNOP(): Unit = startingNode match {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this nop for?


if (conf.slicerConfig.isDefined) {
Slicer(ctx.program, conf.slicerConfig.get).run()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conf.slicerConfig.foreach(sc => Slicer(ctx.program, sc).run())

}

/* Slicing criterion for a given position */
protected def criterion(n: CFGPosition): Set[Variable] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a comment what this is because I'm just guessing just the set of variables the initial criterion is transitively dependent on? Why not include memory etc?

// Iff all out parameters impact criterion.
c.outParams.values.toSet.exists(crit.contains)
// Iff the criterion was directly modified by call.
|| !crit.equals(results.getOrElse(c, Set()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phrasing of 'modifying the criterion' is unclear to me, e.g. if this is a recursive call and the criterion is a fixed point that already captures all its effects its not going to modify the criterion but will still be dependent due to the effects of the call intersecting the criterion?

n match {
case c: DirectCall =>
// Iff all out parameters impact criterion.
c.outParams.values.toSet.exists(crit.contains)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment says all but code is exists

.flatMap(
_.statements.collect { case m: MemoryStore => transferFunctions.convertMemoryIndex(m.index) }.flatten
)
.toSet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably don't need to fix this, but I suspect procedure frames will need to be calculated in a fixed point over the call graph?

@agle agle self-assigned this Aug 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants