Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
FilipKon13 committed Jan 28, 2025
1 parent 2b136e5 commit a3c01b2
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ class PrologueEpilogueHandler(
.map { it is BaseType.Referential } + listOf(false)

// Defined function arguments
for ((ind, destination) in flattenedArguments.zip(isReference).withIndex()) {
require(destination.first is CFGNode.LValue)
for ((ind, value) in flattenedArguments.zip(isReference).withIndex()) {
val (destination, ref) = value
require(destination is CFGNode.LValue)
nodes.add(
(destination.first as CFGNode.LValue) assign
wrapAllocation(callConvention.argumentAllocation(ind), destination.second),
destination assign
wrapAllocation(callConvention.argumentAllocation(ind), ref),
)
}

Expand Down
14 changes: 14 additions & 0 deletions src/test/kotlin/cacophony/controlflow/generation/CFGTestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import cacophony.*
import cacophony.controlflow.*
import cacophony.controlflow.functions.CallGenerator
import cacophony.controlflow.functions.SimpleCallGenerator
import cacophony.semantic.analysis.VariablesMap
import cacophony.semantic.analysis.escapeAnalysis
import cacophony.semantic.syntaxtree.AST
import cacophony.semantic.syntaxtree.BaseType
import cacophony.semantic.syntaxtree.Definition
import cacophony.semantic.syntaxtree.Struct
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk

object MockFunctionParts {
Expand All @@ -22,8 +25,16 @@ internal fun generateSimplifiedCFG(
realPrologue: Boolean = false,
realEpilogue: Boolean = false,
fullCallSequences: Boolean = false,
escapingVariables: Set<Definition> = emptySet(),
): ProgramCFG {
val pipeline = testPipeline()
if (escapingVariables.isNotEmpty()) {
mockkStatic(::escapeAnalysis)
every { escapeAnalysis(any(), any(), any(), any(), any()) } answers {
val variablesMap = arg<VariablesMap>(3)
escapingVariables.map { variablesMap.definitions[it]!! }.toSet()
}
}
val analyzedAST = pipeline.analyzeAst(ast)
val stubbedFunctionHandlers =
analyzedAST.functionHandlers.mapValues { (_, handler) ->
Expand All @@ -36,6 +47,9 @@ internal fun generateSimplifiedCFG(
}
val mockAnalyzedAST = spyk(analyzedAST)
every { mockAnalyzedAST.functionHandlers } returns stubbedFunctionHandlers
if (escapingVariables.isNotEmpty())
every { mockAnalyzedAST.escapeAnalysisResult } returns
escapingVariables.map { mockAnalyzedAST.variablesMap.definitions[it]!! }.toSet()
val callGenerator =
if (fullCallSequences)
SimpleCallGenerator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package cacophony.controlflow.generation
import cacophony.*
import cacophony.codegen.BlockLabel
import cacophony.controlflow.*
import cacophony.controlflow.print.cfgFragmentToGraphviz
import cacophony.controlflow.print.programCfgToBuilder
import cacophony.controlflow.print.programCfgToGraphviz
import cacophony.semantic.syntaxtree.Definition
import org.junit.jupiter.api.Test

Expand Down Expand Up @@ -179,12 +182,118 @@ class FunctionPrologueAndEpilogueTest {

assertFragmentIsEquivalent(actualFragment, expectedFragment)
}
}

// TODO: struct prologue and epilogue test
@Test
fun `variable with ViaPointer allocation has heap space allocated in prologue`() {
// given

/*
* let f = [] -> Unit => (let x = 2;);
* where x escapes
*/
val xDef = variableDeclaration("x", lit(2))
val fDef = unitFunctionDefinition("f", block(xDef, empty()))

// when
val actualCFG = generateCFGWithSimplifiedCalls(fDef, setOf(xDef))

println(programCfgToGraphviz(actualCFG))

println(programCfgToBuilder(actualCFG))

// then
val expectedCFG =
singleFragmentCFG(fDef) {
setupStackFrame("entry", "clean references")
"clean references" does jump("save preserved") { CFGNode.RawCall(BlockLabel("clean_refs")) }
savePreservedRegisters("save preserved", "store static link")
"store static link" does
jump("setup arg 1") {
memoryAccess(registerUse(rbp) sub integer(8)) assign registerUse(rdi)
}
"setup arg 1" does
jump("setup arg 2") {
registerUse(rdi) assign dataLabel("int layout")
}
"setup arg 2" does
jump("call alloc_struct") {
registerUse(rsi) assign registerUse(rbp)
}
"call alloc_struct" does jump("move rax") { call(dataLabel("alloc_struct"), 2) }
"move rax" does jump("write x value") { writeRegister("x", registerUse(rax)) }
"write x value" does
jump("store result") {
memoryAccess(registerUse("x") add integer(0)) assign integer(2)
}
"store result" does jump("restore preserved") { writeRegister("result", CFGNode.UNIT) }
restorePreservedRegisters("restore preserved", "move result to rax")
"move result to rax" does jump("teardown") { writeRegister(rax, "result") }
teardownStackFrame("teardown", "exit")
"exit" does final { returnNode(1) }
}

assertEquivalent(actualCFG, expectedCFG)
}

@Test
fun `variable with ViaPointer allocation on stack has heap space allocated in prologue`() {
// given

/*
* let f = [] -> Unit => (
* let x = 2;
* let g = [] -> Int => x;
* );
* where x escapes
*/
val xDef = variableDeclaration("x", lit(2))
val gDef = intFunctionDefinition("g", variableUse("x"))
val fDef = unitFunctionDefinition("f", block(xDef, gDef, empty()))

// when
val actualCFG = generateCFGWithSimplifiedCalls(fDef, setOf(xDef))[fDef]!!

println(cfgFragmentToGraphviz(actualCFG))

// then
val expectedCFG =
singleFragmentCFG(fDef) {
setupStackFrame("entry", "clean references", 8)
"clean references" does jump("save preserved") { CFGNode.RawCall(BlockLabel("clean_refs")) }
savePreservedRegisters("save preserved", "store static link")
"store static link" does
jump("adjust rsp") {
memoryAccess(registerUse(rbp) sub integer(8)) assign registerUse(rdi)
}
"adjust rsp" does jump("setup arg 1") { registerUse(rsp) subeq integer(8) }
"setup arg 1" does
jump("setup arg 2") {
registerUse(rdi) assign dataLabel("int layout")
}
"setup arg 2" does
jump("call alloc_struct") {
registerUse(rsi) assign registerUse(rbp)
}
"call alloc_struct" does jump("move rax") { call(dataLabel("alloc_struct"), 2) }
"move rax" does jump("fix rsp") { memoryAccess(registerUse(rbp) sub integer(16)) assign registerUse(rax) }
"fix rsp" does jump("write x value") { registerUse(rsp) addeq integer(8) }
"write x value" does
jump("store result") {
memoryAccess(memoryAccess(registerUse(rbp) sub integer(16)) add integer(0)) assign integer(2)
}
"store result" does jump("restore preserved") { writeRegister("result", CFGNode.UNIT) }
restorePreservedRegisters("restore preserved", "move result to rax")
"move result to rax" does jump("teardown") { writeRegister(rax, "result") }
teardownStackFrame("teardown", "exit")
"exit" does final { returnNode(1) }
}

assertFragmentIsEquivalent(actualCFG, expectedCFG[fDef]!!)
}
}

private fun generateCFGWithSimplifiedCalls(definition: Definition.FunctionDefinition) =
generateSimplifiedCFG(definition, realPrologue = true, realEpilogue = true, fullCallSequences = false)
private fun generateCFGWithSimplifiedCalls(definition: Definition.FunctionDefinition, escaping: Set<Definition> = emptySet()) =
generateSimplifiedCFG(definition, realPrologue = true, realEpilogue = true, fullCallSequences = false, escapingVariables = escaping)

private fun CFGFragmentBuilder.savePreservedRegisters(localEntry: String, localExit: String) {
localEntry does jump("save r12") { writeRegister("saved rbx", registerUse(rbx)) }
Expand Down

0 comments on commit a3c01b2

Please sign in to comment.