From 34909f47d8ae897033976f2ab565326eb381d3ce Mon Sep 17 00:00:00 2001 From: jespergravgaard Date: Sun, 22 Apr 2018 23:46:26 +0200 Subject: [PATCH] Recusion now detected, resulting in CompileError. Closes #88 --- .../java/dk/camelot64/kickc/Compiler.java | 1 + .../kickc/passes/Pass1AssertNoRecursion.java | 74 +++++++++++++++++++ .../dk/camelot64/kickc/test/TestPrograms.java | 11 +++ .../kickc/test/kc/recursion-error-complex.kc | 35 +++++++++ .../kickc/test/kc/recursion-error.kc | 14 ++++ 5 files changed, 135 insertions(+) create mode 100644 src/main/java/dk/camelot64/kickc/passes/Pass1AssertNoRecursion.java create mode 100644 src/test/java/dk/camelot64/kickc/test/kc/recursion-error-complex.kc create mode 100644 src/test/java/dk/camelot64/kickc/test/kc/recursion-error.kc diff --git a/src/main/java/dk/camelot64/kickc/Compiler.java b/src/main/java/dk/camelot64/kickc/Compiler.java index a0a4cee11..d36c0d126 100644 --- a/src/main/java/dk/camelot64/kickc/Compiler.java +++ b/src/main/java/dk/camelot64/kickc/Compiler.java @@ -120,6 +120,7 @@ public class Compiler { new Pass1AssertNoLValueIntermediate(program).execute(); new Pass1AddTypePromotions(program).execute(); new Pass1AssertArrayLengths(program).execute(); + new Pass1AssertNoRecursion(program).execute(); getLog().append("INITIAL CONTROL FLOW GRAPH"); getLog().append(program.getGraph().toString(program)); diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass1AssertNoRecursion.java b/src/main/java/dk/camelot64/kickc/passes/Pass1AssertNoRecursion.java new file mode 100644 index 000000000..6cd05209a --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/passes/Pass1AssertNoRecursion.java @@ -0,0 +1,74 @@ +package dk.camelot64.kickc.passes; + +import dk.camelot64.kickc.model.CompileError; +import dk.camelot64.kickc.model.ControlFlowBlock; +import dk.camelot64.kickc.model.ControlFlowGraph; +import dk.camelot64.kickc.model.Program; +import dk.camelot64.kickc.model.statements.Statement; +import dk.camelot64.kickc.model.statements.StatementCall; +import dk.camelot64.kickc.model.values.ProcedureRef; +import dk.camelot64.kickc.model.values.SymbolRef; + +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; + +/** Asserts that the program has no recursive calls */ +public class Pass1AssertNoRecursion extends Pass1Base { + + public Pass1AssertNoRecursion(Program program) { + super(program); + } + + @Override + public boolean step() { + ControlFlowBlock firstBlock = getGraph().getFirstBlock(); + Deque path = new LinkedList<>(); + assertNoRecursion(firstBlock, path); + return false; + } + + /** + * Asserts that no methods perform recursive calls + * @param block The block to check + * @param path The call-path taken to the block + */ + private void assertNoRecursion(ControlFlowBlock block, Deque path) { + // Detect recursion + if(path.contains(block)) { + StringBuffer msg = new StringBuffer(); + Iterator pathIt = path.descendingIterator(); + while(pathIt.hasNext()) { + ControlFlowBlock pathBlock = pathIt.next(); + msg.append(pathBlock.getLabel()).append(" > "); + } + msg.append(block.getLabel()); + throw new CompileError("ERROR! Recursion not allowed! "+msg); + } + path.push(block); + // Follow all calls + for(Statement statement : block.getStatements()) { + if(statement instanceof StatementCall) { + ProcedureRef procedureRef = ((StatementCall) statement).getProcedure(); + ControlFlowBlock procedureBlock = getGraph().getBlock(procedureRef.getLabelRef()); + assertNoRecursion(procedureBlock, path); + } + } + // Follow successors + if(block.getConditionalSuccessor()!=null && !block.getConditionalSuccessor().isProcExit()) { + ControlFlowBlock conditionalSuccessor = getGraph().getConditionalSuccessor(block); + if(!path.contains(conditionalSuccessor)) { + assertNoRecursion(conditionalSuccessor, path); + } + } + if(block.getDefaultSuccessor()!=null && !block.getDefaultSuccessor().isProcExit()) { + ControlFlowBlock defaultSuccessor = getGraph().getDefaultSuccessor(block); + if(!path.contains(defaultSuccessor)) { + assertNoRecursion(defaultSuccessor, path); + } + } + path.pop(); + } + + +} diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 686c02208..5a06cc286 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -710,6 +710,17 @@ public class TestPrograms { assertError("register-clobber", "CLOBBER ERROR"); } + @Test + public void testRecursionError() throws IOException, URISyntaxException { + assertError("recursion-error", "Recursion"); + } + + @Test + public void testRecursionComplexError() throws IOException, URISyntaxException { + assertError("recursion-error-complex", "Recursion"); + } + + private void assertError(String kcFile, String expectError) throws IOException, URISyntaxException { try { compileAndCompare(kcFile); diff --git a/src/test/java/dk/camelot64/kickc/test/kc/recursion-error-complex.kc b/src/test/java/dk/camelot64/kickc/test/kc/recursion-error-complex.kc new file mode 100644 index 000000000..e0a38aaad --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/kc/recursion-error-complex.kc @@ -0,0 +1,35 @@ +// Test that a complex recursion results in a CompileError + +void main() { + byte* screen = $400; + byte f = fa(8); + *screen = f; +} + +// Recursive function +byte fa(byte n) { + if(n<10) { + return fb(n-1); + } else { + return fc(n+1); + } +} + +// Recursive function +byte fb(byte n) { + if((n&1)==0) { + return fc(n); + } else { + return n; + } +} + +// Recursive function +byte fc(byte n) { + if((n&1)==0) { + return n; + } else { + return fa(n>>1); + } +} + diff --git a/src/test/java/dk/camelot64/kickc/test/kc/recursion-error.kc b/src/test/java/dk/camelot64/kickc/test/kc/recursion-error.kc new file mode 100644 index 000000000..bdb62a543 --- /dev/null +++ b/src/test/java/dk/camelot64/kickc/test/kc/recursion-error.kc @@ -0,0 +1,14 @@ +// Test that recursion results in a CompileError, as it is not supported + +void main() { + byte* screen = $400; + byte f = fib(8); + *screen = f; +} + +// Fibonacci implementation +byte fib(byte n) { + if(n==0) return 0; + if(n==1) return 1; + return fib(n-1)+fib(n-2); +} \ No newline at end of file