From a180a4c0b48a4efc020404e495c2e0c0179bf7ab Mon Sep 17 00:00:00 2001 From: jespergravgaard Date: Sat, 8 Jun 2019 15:01:44 +0200 Subject: [PATCH] Implemented call parameter type checking in pass 1. --- .../java/dk/camelot64/kickc/Compiler.java | 2 +- .../Pass1AssertProcedureCallParameters.java | 60 ++++++++++++ .../kickc/passes/Pass1UnwindStructValues.java | 95 +++++++++++-------- .../dk/camelot64/kickc/test/TestPrograms.java | 26 +++-- src/test/kc/struct-err-2.kc | 14 +++ src/test/kc/struct-err-3.kc | 17 ++++ src/test/ref/struct-3.log | 63 ------------ 7 files changed, 164 insertions(+), 113 deletions(-) create mode 100644 src/main/java/dk/camelot64/kickc/passes/Pass1AssertProcedureCallParameters.java create mode 100644 src/test/kc/struct-err-2.kc create mode 100644 src/test/kc/struct-err-3.kc diff --git a/src/main/java/dk/camelot64/kickc/Compiler.java b/src/main/java/dk/camelot64/kickc/Compiler.java index 1cf34db63..698027e6f 100644 --- a/src/main/java/dk/camelot64/kickc/Compiler.java +++ b/src/main/java/dk/camelot64/kickc/Compiler.java @@ -164,7 +164,7 @@ public class Compiler { new Pass1Procedures(program).execute(); new PassNTypeInference(program).execute(); new PassNTypeIdSimplification(program).execute(); - + new Pass1AssertProcedureCallParameters(program).execute(); new Pass1UnwindStructValues(program).execute(); if(getLog().isVerbosePass1CreateSsa()) { diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass1AssertProcedureCallParameters.java b/src/main/java/dk/camelot64/kickc/passes/Pass1AssertProcedureCallParameters.java new file mode 100644 index 000000000..fc869c208 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/passes/Pass1AssertProcedureCallParameters.java @@ -0,0 +1,60 @@ +package dk.camelot64.kickc.passes; + +import dk.camelot64.kickc.model.CompileError; +import dk.camelot64.kickc.model.ControlFlowBlock; +import dk.camelot64.kickc.model.Program; +import dk.camelot64.kickc.model.statements.Statement; +import dk.camelot64.kickc.model.statements.StatementAssignment; +import dk.camelot64.kickc.model.statements.StatementCall; +import dk.camelot64.kickc.model.statements.StatementConditionalJump; +import dk.camelot64.kickc.model.symbols.Procedure; +import dk.camelot64.kickc.model.symbols.Variable; +import dk.camelot64.kickc.model.types.SymbolType; +import dk.camelot64.kickc.model.types.SymbolTypeConversion; +import dk.camelot64.kickc.model.types.SymbolTypeInference; +import dk.camelot64.kickc.model.values.*; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; + +/** + * Asserts that all calls have parameters of the types that the procedure expects + */ +public class Pass1AssertProcedureCallParameters extends Pass1Base { + + public Pass1AssertProcedureCallParameters(Program program) { + super(program); + } + + @Override + public boolean step() { + for(ControlFlowBlock block : getGraph().getAllBlocks()) { + for(Statement statement : block.getStatements()) { + if(statement instanceof StatementCall) { + StatementCall call = (StatementCall) statement; + Procedure procedure = getScope().getProcedure(call.getProcedure()); + List declParameters = procedure.getParameters(); + List callParameters = call.getParameters(); + if(callParameters.size()!=declParameters.size()) { + throw new CompileError("Wrong number of parameters in call "+call.toString(getProgram(), false)+" expected "+procedure.toString(getProgram()), statement); + } + for(int i = 0; i < declParameters.size(); i++) { + Variable declParameter = declParameters.get(i); + RValue callParameter = callParameters.get(i); + SymbolType callParameterType = SymbolTypeInference.inferType(getScope(), callParameter); + SymbolType declParameterType = declParameter.getType(); + if(!SymbolTypeConversion.assignmentTypeMatch(declParameterType, callParameterType)) { + throw new CompileError("Parameters type mismatch in call "+call.toString(getProgram(), false)+" expected "+procedure.toString(getProgram()), statement); + } + } + } + } + } + + return false; + } + + +} diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass1UnwindStructValues.java b/src/main/java/dk/camelot64/kickc/passes/Pass1UnwindStructValues.java index b94cee6d3..a55d38054 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass1UnwindStructValues.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass1UnwindStructValues.java @@ -51,8 +51,9 @@ public class Pass1UnwindStructValues extends Pass1Base { } } } - for(Procedure procedure : getScope().getAllProcedures(true)) { + // Iterate through all procedures changing parameter lists by unwinding each struct value parameter + for(Procedure procedure : getScope().getAllProcedures(true)) { ArrayList unwoundParameterNames = new ArrayList<>(); boolean procedureUnwound = false; for(Variable parameter : procedure.getParameters()) { @@ -72,8 +73,7 @@ public class Pass1UnwindStructValues extends Pass1Base { } } - - // Unwind all references to full structs + // Unwind all references to struct values into members for(ControlFlowBlock block : getGraph().getAllBlocks()) { ListIterator stmtIt = block.getStatements().listIterator(); while(stmtIt.hasNext()) { @@ -83,44 +83,7 @@ public class Pass1UnwindStructValues extends Pass1Base { if(assignment.getlValue() instanceof VariableRef) { Variable assignedVar = getScope().getVariable((VariableRef) assignment.getlValue()); if(assignedVar.getType() instanceof SymbolTypeStruct) { - // Assigning a struct! - if(assignment.getOperator() == null && assignment.getrValue2() instanceof StructZero) { - // Initializing a struct - unwind to assigning zero to each member! - StructUnwinding.VariableUnwinding variableUnwinding = structUnwinding.getVariableUnwinding(assignedVar.getRef()); - if(variableUnwinding != null) { - stmtIt.previous(); - for(String memberName : variableUnwinding.getMemberNames()) { - VariableRef memberVarRef = variableUnwinding.getMemberUnwinding(memberName); - Variable memberVar = getScope().getVariable(memberVarRef); - Statement initStmt = Pass0GenerateStatementSequence.createDefaultInitializationStatement(memberVarRef, memberVar.getType(), statement.getSource(), Comment.NO_COMMENTS); - stmtIt.add(initStmt); - getLog().append("Adding struct value member variable default initializer " + initStmt.toString(getProgram(), false)); - } - stmtIt.next(); - stmtIt.remove(); - } - } else if(assignment.getOperator() == null && assignment.getrValue2() instanceof VariableRef) { - Variable sourceVar = getScope().getVariable((VariableRef) assignment.getrValue2()); - if(sourceVar.getType().equals(assignedVar.getType())) { - // Copying a struct - unwind to assigning each member! - StructUnwinding.VariableUnwinding assignedMemberVariables = structUnwinding.getVariableUnwinding(assignedVar.getRef()); - StructUnwinding.VariableUnwinding sourceMemberVariables = structUnwinding.getVariableUnwinding(sourceVar.getRef()); - if(assignedMemberVariables != null && sourceMemberVariables != null) { - stmtIt.previous(); - for(String memberName : assignedMemberVariables.getMemberNames()) { - VariableRef assignedMemberVarRef = assignedMemberVariables.getMemberUnwinding(memberName); - VariableRef sourceMemberVarRef = sourceMemberVariables.getMemberUnwinding(memberName); - Statement copyStmt = new StatementAssignment(assignedMemberVarRef, sourceMemberVarRef, statement.getSource(), Comment.NO_COMMENTS); - stmtIt.add(copyStmt); - getLog().append("Adding struct value member variable copy " + copyStmt.toString(getProgram(), false)); - } - stmtIt.next(); - stmtIt.remove(); - } - } else { - throw new CompileError("Incompatible struct assignment " + statement.toString(getProgram(), false), statement); - } - } + unwindStructAssignment(assignment, assignedVar, stmtIt, structUnwinding); } } } else if(statement instanceof StatementCall) { @@ -179,6 +142,56 @@ public class Pass1UnwindStructValues extends Pass1Base { return modified; } + /** + * Unwind an assignment to a struct value variable into assignment of each member + * @param assignment The assignment statement + * @param assignedVar The struct value variable being assigned to (the LValue) + * @param stmtIt The statement iterator used for adding/removing statements + * @param structUnwinding Information about unwound struct value variables + */ + public void unwindStructAssignment(StatementAssignment assignment, Variable assignedVar, ListIterator stmtIt, StructUnwinding structUnwinding) { + // Assigning a struct! + if(assignment.getOperator() == null && assignment.getrValue2() instanceof StructZero) { + // Initializing a struct - unwind to assigning zero to each member! + StructUnwinding.VariableUnwinding variableUnwinding = structUnwinding.getVariableUnwinding(assignedVar.getRef()); + if(variableUnwinding != null) { + stmtIt.previous(); + for(String memberName : variableUnwinding.getMemberNames()) { + VariableRef memberVarRef = variableUnwinding.getMemberUnwinding(memberName); + Variable memberVar = getScope().getVariable(memberVarRef); + Statement initStmt = Pass0GenerateStatementSequence.createDefaultInitializationStatement(memberVarRef, memberVar.getType(), assignment.getSource(), Comment.NO_COMMENTS); + stmtIt.add(initStmt); + getLog().append("Adding struct value member variable default initializer " + initStmt.toString(getProgram(), false)); + } + stmtIt.next(); + stmtIt.remove(); + } + } else if(assignment.getOperator() == null && assignment.getrValue2() instanceof VariableRef) { + Variable sourceVar = getScope().getVariable((VariableRef) assignment.getrValue2()); + if(sourceVar.getType().equals(assignedVar.getType())) { + // Copying a struct - unwind to assigning each member! + StructUnwinding.VariableUnwinding assignedMemberVariables = structUnwinding.getVariableUnwinding(assignedVar.getRef()); + StructUnwinding.VariableUnwinding sourceMemberVariables = structUnwinding.getVariableUnwinding(sourceVar.getRef()); + if(assignedMemberVariables != null && sourceMemberVariables != null) { + stmtIt.previous(); + for(String memberName : assignedMemberVariables.getMemberNames()) { + VariableRef assignedMemberVarRef = assignedMemberVariables.getMemberUnwinding(memberName); + VariableRef sourceMemberVarRef = sourceMemberVariables.getMemberUnwinding(memberName); + Statement copyStmt = new StatementAssignment(assignedMemberVarRef, sourceMemberVarRef, assignment.getSource(), Comment.NO_COMMENTS); + stmtIt.add(copyStmt); + getLog().append("Adding struct value member variable copy " + copyStmt.toString(getProgram(), false)); + } + stmtIt.next(); + stmtIt.remove(); + } + } else { + throw new CompileError("Incompatible struct assignment " + assignment.toString(getProgram(), false), assignment); + } + } else { + throw new CompileError("Incompatible struct assignment " + assignment.toString(getProgram(), false), assignment); + } + } + /** * Keeps track of all structs that have been unwound into member variables. */ diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 1c51f1a8b..fa727a7b7 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -51,43 +51,53 @@ public class TestPrograms { } @Test - public void testError1() throws IOException, URISyntaxException { + public void testStructError3() throws IOException, URISyntaxException { + assertError("struct-err-3", "Parameters type mismatch in call"); + } + + @Test + public void testStructError2() throws IOException, URISyntaxException { + assertError("struct-err-2", "Incompatible struct assignment"); + } + + @Test + public void testStructError1() throws IOException, URISyntaxException { assertError("struct-err-1", "Incompatible struct assignment"); } @Test - public void testError0() throws IOException, URISyntaxException { + public void testStructError0() throws IOException, URISyntaxException { assertError("struct-err-0", "Unknown struct type"); } @Test public void testStruct5() throws IOException, URISyntaxException { - compileAndCompare("struct-5", log()); + compileAndCompare("struct-5"); } @Test public void testStruct4() throws IOException, URISyntaxException { - compileAndCompare("struct-4", log()); + compileAndCompare("struct-4"); } @Test public void testStruct3() throws IOException, URISyntaxException { - compileAndCompare("struct-3", log().verboseCreateSsa()); + compileAndCompare("struct-3"); } @Test public void testStruct2() throws IOException, URISyntaxException { - compileAndCompare("struct-2", log()); + compileAndCompare("struct-2"); } @Test public void testStruct1() throws IOException, URISyntaxException { - compileAndCompare("struct-1", log()); + compileAndCompare("struct-1"); } @Test public void testStruct0() throws IOException, URISyntaxException { - compileAndCompare("struct-0", log()); + compileAndCompare("struct-0"); } @Test diff --git a/src/test/kc/struct-err-2.kc b/src/test/kc/struct-err-2.kc new file mode 100644 index 000000000..538129894 --- /dev/null +++ b/src/test/kc/struct-err-2.kc @@ -0,0 +1,14 @@ +// Incompatible struct assignment + +struct Point { + byte x; + byte y; +}; + +void main() { + struct Point p1; + p1 = 4; + const byte* SCREEN = 0x0400; + SCREEN[0] = p1.x; +} + diff --git a/src/test/kc/struct-err-3.kc b/src/test/kc/struct-err-3.kc new file mode 100644 index 000000000..a2ba69e92 --- /dev/null +++ b/src/test/kc/struct-err-3.kc @@ -0,0 +1,17 @@ +// Incompatible struct parameter + +struct Point { + byte x; + byte y; +}; + +void main() { + print(7); +} + +const byte* SCREEN = 0x0400; + +void print(struct Point p) { + *SCREEN = p.x; +} + diff --git a/src/test/ref/struct-3.log b/src/test/ref/struct-3.log index 52228b6e0..494ed7ad0 100644 --- a/src/test/ref/struct-3.log +++ b/src/test/ref/struct-3.log @@ -14,70 +14,7 @@ Replacing struct member reference (struct Point) main::p1.y with member variable Replacing struct member reference (struct Point) main::p1.x with member variable reference (byte) main::p1_x Replacing struct member reference (struct Point) print::p.x with member variable reference (byte) print::p_x Replacing struct member reference (struct Point) print::p.y with member variable reference (byte) print::p_y -SYMBOLS -(label) @1 -(label) @2 -(label) @begin -(label) @end -(byte) Point::x -(byte) Point::y -(byte*) SCREEN -(byte) idx -(void()) main() -(void~) main::$0 -(void~) main::$1 -(label) main::@return -(struct Point) main::p1 -(byte) main::p1_x -(byte) main::p1_y -(void()) print((byte) print::p_x , (byte) print::p_y) -(label) print::@return -(struct Point) print::p -(byte) print::p_x -(byte) print::p_y - Adding pointer type conversion cast (byte*) SCREEN in (byte*) SCREEN ← (number) $400 -INITIAL CONTROL FLOW GRAPH -@begin: scope:[] from - to:@1 -main: scope:[main] from - [0] (byte) main::p1_x ← (byte) 0 - [1] (byte) main::p1_y ← (byte) 0 - [2] (byte) main::p1_x ← (number) 1 - [3] (byte) main::p1_y ← (number) 4 - [4] (void~) main::$0 ← call print (byte) main::p1_x (byte) main::p1_y - [5] (byte) main::p1_x ← (number) 2 - [6] (void~) main::$1 ← call print (byte) main::p1_x (byte) main::p1_y - to:main::@return -main::@return: scope:[main] from main - [7] return - to:@return -@1: scope:[] from @begin - [8] (byte*) SCREEN ← ((byte*)) (number) $400 - [9] (byte) idx ← (number) 0 - to:@2 -print: scope:[print] from - [10] *((byte*) SCREEN + (byte) idx) ← (byte) print::p_x - [11] (byte) idx ← ++ (byte) idx - [12] *((byte*) SCREEN + (byte) idx) ← (byte) print::p_y - [13] (byte) idx ← ++ (byte) idx - to:print::@return -print::@return: scope:[print] from print - [14] return - to:@return -@2: scope:[] from @1 - [15] call main - to:@end -@end: scope:[] from @2 - -Eliminating unused variable - keeping the call (void~) main::$0 -Eliminating unused variable - keeping the call (void~) main::$1 -PROCEDURE MODIFY VARIABLE ANALYSIS -main modifies idx -print modifies idx - -Completing Phi functions... -Completing Phi functions... CONTROL FLOW GRAPH SSA @begin: scope:[] from