diff --git a/src/main/kotlin/cacophony/controlflow/functions/PrologueEpilogueHandler.kt b/src/main/kotlin/cacophony/controlflow/functions/PrologueEpilogueHandler.kt index 5984d178..76a7cdd5 100644 --- a/src/main/kotlin/cacophony/controlflow/functions/PrologueEpilogueHandler.kt +++ b/src/main/kotlin/cacophony/controlflow/functions/PrologueEpilogueHandler.kt @@ -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), ) } diff --git a/src/test/kotlin/cacophony/controlflow/generation/CFGTestUtils.kt b/src/test/kotlin/cacophony/controlflow/generation/CFGTestUtils.kt index 9000dfb5..f67b68d8 100644 --- a/src/test/kotlin/cacophony/controlflow/generation/CFGTestUtils.kt +++ b/src/test/kotlin/cacophony/controlflow/generation/CFGTestUtils.kt @@ -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 { @@ -22,8 +25,16 @@ internal fun generateSimplifiedCFG( realPrologue: Boolean = false, realEpilogue: Boolean = false, fullCallSequences: Boolean = false, + escapingVariables: Set = emptySet(), ): ProgramCFG { val pipeline = testPipeline() + if (escapingVariables.isNotEmpty()) { + mockkStatic(::escapeAnalysis) + every { escapeAnalysis(any(), any(), any(), any(), any()) } answers { + val variablesMap = arg(3) + escapingVariables.map { variablesMap.definitions[it]!! }.toSet() + } + } val analyzedAST = pipeline.analyzeAst(ast) val stubbedFunctionHandlers = analyzedAST.functionHandlers.mapValues { (_, handler) -> @@ -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() diff --git a/src/test/kotlin/cacophony/controlflow/generation/FunctionPrologueAndEpilogueTest.kt b/src/test/kotlin/cacophony/controlflow/generation/FunctionPrologueAndEpilogueTest.kt index 45f2afc9..d9d9c4b3 100644 --- a/src/test/kotlin/cacophony/controlflow/generation/FunctionPrologueAndEpilogueTest.kt +++ b/src/test/kotlin/cacophony/controlflow/generation/FunctionPrologueAndEpilogueTest.kt @@ -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 @@ -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 = 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)) }