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

Partial evaluation #408

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions .idea/runConfigurations.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions cli/src/main/kotlin/edu/cornell/cs/apl/viaduct/cli/Evaluate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package edu.cornell.cs.apl.viaduct.cli

import com.github.ajalt.clikt.core.CliktCommand
import edu.cornell.cs.apl.viaduct.backends.DefaultCombinedBackend
import edu.cornell.cs.apl.viaduct.lowering.LoweringPass
import edu.cornell.cs.apl.viaduct.lowering.PartialEvaluator
import edu.cornell.cs.apl.viaduct.lowering.computeDominators
import edu.cornell.cs.apl.viaduct.lowering.computePostdominators
import edu.cornell.cs.apl.viaduct.lowering.computeTransitiveSuccessors
import edu.cornell.cs.apl.viaduct.lowering.optimize
import edu.cornell.cs.apl.viaduct.parsing.parse
import edu.cornell.cs.apl.viaduct.passes.elaborated
import java.io.File

class Evaluate : CliktCommand(help = "Partially evaluate compiled program.") {
private val input: File? by inputProgram()

override fun run() {
val program =
input.sourceFile()
.parse(DefaultCombinedBackend.protocolParsers)
.elaborated()

val flowchart = LoweringPass.get(program).flowchartProgram.optimize()
println("original")
println(flowchart.toDocument().print())

println("dominators")
for (kv in flowchart.computeDominators()) {
println("${kv.key} => ${kv.value}")
}

println("postdominators")
for (kv in flowchart.computePostdominators()) {
println("${kv.key} => ${kv.value}")
}

println("transitive successors")
for (kv in flowchart.computeTransitiveSuccessors()) {
println("${kv.key} => ${kv.value}")
}

val residualFlowchart = PartialEvaluator.evaluate(flowchart).optimize()
println("\nresidual")
println(residualFlowchart.toDocument().print())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Viaduct : NoOpCliktCommand(help = "Compile high level specifications to se

init {
versionOption(version)
subcommands(Format(), Compile(), CompletionCommand(), Run())
subcommands(Format(), Compile(), CompletionCommand(), Run(), Evaluate())
// TODO: Help, Interpret, commands
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package edu.cornell.cs.apl.viaduct.lowering

import edu.cornell.cs.apl.viaduct.algebra.MeetSemiLattice
import edu.cornell.cs.apl.viaduct.syntax.ObjectVariable
import edu.cornell.cs.apl.viaduct.syntax.Temporary
import edu.cornell.cs.apl.viaduct.util.FreshNameGenerator
import edu.cornell.cs.apl.viaduct.util.dataflow.DataFlowNode
import edu.cornell.cs.apl.viaduct.util.dataflow.IdentityEdge
import edu.cornell.cs.apl.viaduct.util.dataflow.solveDataFlow
import org.jgrapht.graph.DefaultDirectedGraph

enum class BindingTime : MeetSemiLattice<BindingTime> {
STATIC, DYNAMIC;

override fun meet(that: BindingTime): BindingTime =
if (this == DYNAMIC || that == DYNAMIC) DYNAMIC else STATIC

override fun lessThanOrEqualTo(that: BindingTime): Boolean =
this == DYNAMIC || that == STATIC
}

sealed class BindingTimeAnalysisNode : DataFlowNode<BindingTime>

data class ObjectBindingTimeNode(val variable: ObjectVariable) : BindingTimeAnalysisNode() {
override fun transfer(input: BindingTime): BindingTime = input
}

data class TemporaryBindingTimeNode(val temporary: Temporary) : BindingTimeAnalysisNode() {
override fun transfer(input: BindingTime): BindingTime = input
}

data class BlockBindingTimeNode(val block: RegularBlockLabel) : BindingTimeAnalysisNode() {
override fun transfer(input: BindingTime): BindingTime = input
}

data class ConstantBindingTimeNode(val value: BindingTime) : BindingTimeAnalysisNode() {
override fun transfer(input: BindingTime): BindingTime = value
}

data class VariableBindingTimeNode(val name: String) : BindingTimeAnalysisNode() {
override fun transfer(input: BindingTime): BindingTime = input
}

class BindingTimeAnalysis private constructor(val program: FlowchartProgram) {
companion object {
fun computeBindingTime(program: FlowchartProgram): Map<ObjectVariable, BindingTime> {
return BindingTimeAnalysis(program).computeObjectBindingTime()
}
}

private val graph = DefaultDirectedGraph<BindingTimeAnalysisNode, IdentityEdge<BindingTime>>(null, null, false)
private val nameGenerator = FreshNameGenerator()

private val objectNodes = mutableMapOf<ObjectVariable, ObjectBindingTimeNode>()
private val temporaryNodes = mutableMapOf<Temporary, TemporaryBindingTimeNode>()
private val blockNodes = mutableMapOf<RegularBlockLabel, BlockBindingTimeNode>()

private fun objectNode(variable: ObjectVariable): ObjectBindingTimeNode =
objectNodes.getOrPut(variable) { ObjectBindingTimeNode(variable) }

private fun temporaryNode(temporary: Temporary): TemporaryBindingTimeNode =
temporaryNodes.getOrPut(temporary) { TemporaryBindingTimeNode(temporary) }

private fun blockNode(label: RegularBlockLabel): BlockBindingTimeNode =
blockNodes.getOrPut(label) { BlockBindingTimeNode(label) }

private fun getFreshVariable(): VariableBindingTimeNode {
return VariableBindingTimeNode(nameGenerator.getFreshName("var"))
}

private fun flowsTo(lhs: BindingTimeAnalysisNode, rhs: BindingTimeAnalysisNode) {
graph.addVertex(lhs)
graph.addVertex(rhs)
graph.addEdge(lhs, rhs, IdentityEdge())
}

private fun generateBindingTimeConstraints(expr: LoweredExpression): BindingTimeAnalysisNode =
when (expr) {
is InputNode -> ConstantBindingTimeNode(BindingTime.DYNAMIC)

is LiteralNode -> ConstantBindingTimeNode(BindingTime.STATIC)

is OperatorApplicationNode -> {
val node = getFreshVariable()

expr.arguments.map {
generateBindingTimeConstraints(it)
}.forEach { argNode -> flowsTo(argNode, node) }

node
}

is QueryNode -> {
val node = getFreshVariable()

flowsTo(objectNode(expr.variable), node)
expr.arguments.map {
generateBindingTimeConstraints(it)
}.forEach { argNode -> flowsTo(argNode, node) }

node
}

is ReadNode -> temporaryNode(expr.temporary)
}

private fun generateBindingTimeConstraints(block: RegularBlockLabel, stmt: LoweredStatement) {
when (stmt) {
is DeclarationNode -> {
val node = objectNode(stmt.name)

stmt.arguments.map {
generateBindingTimeConstraints(it)
}.forEach { argNode -> flowsTo(argNode, node) }

// flowsTo(blockNode(block), node)
}

is LetNode -> {
val node = temporaryNode(stmt.temporary)
val valueNode = generateBindingTimeConstraints(stmt.value)
flowsTo(valueNode, node)
}

is OutputNode -> {}

SkipNode -> {}

is UpdateNode -> {
val node = objectNode(stmt.variable)
stmt.arguments.map {
generateBindingTimeConstraints(it)
}.forEach { argNode -> flowsTo(argNode, node) }

flowsTo(blockNode(block), node)
}
}
}

private fun generateBindingTimeConstraints(
transitiveSuccessors: Set<RegularBlockLabel>,
postdominators: Set<RegularBlockLabel>,
label: RegularBlockLabel,
block: LoweredBasicBlock<RegularBlockLabel>
) {
// generate constraints from statements
for (stmt in block.statements) {
generateBindingTimeConstraints(label, stmt)
}

// add PC label constraints: if this block has a conditional jump,
// there is an implicit flow from the guard's label to
// the PC labels of transitive successor blocks that are *not* postdominators
when (val jump = block.jump) {
is GotoIf -> {
val guardNode = generateBindingTimeConstraints(jump.guard)
for (successor in transitiveSuccessors) {
if (!postdominators.contains(successor)) {
flowsTo(guardNode, blockNode(successor))
}
}
}

else -> {}
}
}

fun computeObjectBindingTime(): Map<ObjectVariable, BindingTime> {
val postdominatorMap = program.computePostdominators()
val transitiveSuccessorMap = program.computeTransitiveSuccessors()

for (kv in program.blocks) {
generateBindingTimeConstraints(
postdominatorMap[kv.key]!!,
transitiveSuccessorMap[kv.key]!!,
kv.key,
kv.value
)
}

val solution = solveDataFlow(BindingTime.STATIC, graph)
return objectNodes.map { kv -> kv.key to solution[kv.value]!! }.toMap()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package edu.cornell.cs.apl.viaduct.lowering

import edu.cornell.cs.apl.viaduct.algebra.MeetSemiLattice
import edu.cornell.cs.apl.viaduct.util.dataflow.DataFlowNode
import edu.cornell.cs.apl.viaduct.util.dataflow.IdentityEdge
import edu.cornell.cs.apl.viaduct.util.dataflow.solveDataFlow
import kotlinx.collections.immutable.PersistentSet
import kotlinx.collections.immutable.intersect
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toPersistentSet
import org.jgrapht.graph.DefaultDirectedGraph

private sealed class AbstractDataFlowSet<T, Self : AbstractDataFlowSet<T, Self>> : MeetSemiLattice<Self> {
abstract val set: PersistentSet<T>
}

/** Powerset element. */
private data class DataFlowSet<T>(override val set: PersistentSet<T>) : AbstractDataFlowSet<T, DataFlowSet<T>>() {
override fun meet(that: DataFlowSet<T>): DataFlowSet<T> {
return DataFlowSet(this.set.intersect(that.set))
}

override fun lessThanOrEqualTo(that: DataFlowSet<T>): Boolean =
this.set.containsAll(that.set)
}

/** Inverse powerset element (bottom is universe set, meet is union). */
private data class InvertedDataFlowSet<T>(override val set: PersistentSet<T>) : AbstractDataFlowSet<T, InvertedDataFlowSet<T>>() {
override fun meet(that: InvertedDataFlowSet<T>): InvertedDataFlowSet<T> {
return InvertedDataFlowSet(this.set.addAll(that.set))
}

override fun lessThanOrEqualTo(that: InvertedDataFlowSet<T>): Boolean =
that.set.containsAll(this.set)
}

private data class DominatorAnalysisNode<S : AbstractDataFlowSet<RegularBlockLabel, S>>(
val label: RegularBlockLabel,
val isEntryPoint: Boolean,
val nodeBuilder: (PersistentSet<RegularBlockLabel>) -> S
) : DataFlowNode<S> {
override fun transfer(input: S): S {
// recall that the top element is all labels; if we don't special case entry points,
// then the analysis will compute top for all nodes
return if (isEntryPoint) {
nodeBuilder(persistentSetOf(label))
} else {
nodeBuilder(input.set.add(label))
}
}
}

/** Perform (strict) dominator analysis by dataflow analysis.
* The lattice is the powerset lattice (top = set of all block labels),
* while the transfer function is defined by:
* - in(in_nodes) = fold in_nodes TOP (acc in_node -> intersect acc out(in_node))
* - out(n) = {n} union in(n)
* - out(START) = {START} (important that you special case entry points!)
* */
private fun <S : AbstractDataFlowSet<RegularBlockLabel, S>> computeDominators(
successorMap: Map<RegularBlockLabel, Set<RegularBlockLabel>>,
entryPoints: Set<RegularBlockLabel>,
nodeBuilder: (PersistentSet<RegularBlockLabel>) -> S
): Map<RegularBlockLabel, Set<RegularBlockLabel>> {
val graph = DefaultDirectedGraph<DominatorAnalysisNode<S>, IdentityEdge<S>>(null, null, false)
val nodeMap = successorMap.keys.associateWith { label ->
DominatorAnalysisNode(label, entryPoints.contains(label), nodeBuilder)
}

for (kv in successorMap) {
val src = nodeMap[kv.key]!!
graph.addVertex(src)

for (successor in kv.value) {
val dst = nodeMap[successor]!!
val edge = IdentityEdge<S>()
graph.addVertex(dst)
graph.addEdge(src, dst, edge)
}
}

// return blocks that are *strictly* dominated
return solveDataFlow(nodeBuilder(nodeMap.keys.toPersistentSet()), graph).map { kv ->
kv.key.label to kv.value.set.filter { v -> v != kv.key.label }.toSet()
}.toMap()
}

/** Compute dominators for a flowchart program. */
fun FlowchartProgram.computeDominators(): Map<RegularBlockLabel, Set<RegularBlockLabel>> {
return computeDominators(this.successorMap, setOf(ENTRY_POINT_LABEL)) { set -> DataFlowSet(set) }
}

/** Compute postdominators for a flowchart program
* by computing dominators in inverse CFG. */
fun FlowchartProgram.computePostdominators(): Map<RegularBlockLabel, Set<RegularBlockLabel>> {
return computeDominators(this.predecessorMap, this.exitPoints) { set -> DataFlowSet(set) }
}
/** Compute transitive successors for a flowchart program.
* Similar to postdominator analysis, except invert lattice so the meet operator is union instead of intersect:
* We care about successors from *any* path, not *all* paths.
* */
fun FlowchartProgram.computeTransitiveSuccessors(): Map<RegularBlockLabel, Set<RegularBlockLabel>> {
return computeDominators(this.predecessorMap, this.exitPoints) { set -> InvertedDataFlowSet(set) }
}
Loading