diff --git a/src/main/java/dk/camelot64/kickc/Compiler.java b/src/main/java/dk/camelot64/kickc/Compiler.java index 4d524e483..6440f0256 100644 --- a/src/main/java/dk/camelot64/kickc/Compiler.java +++ b/src/main/java/dk/camelot64/kickc/Compiler.java @@ -233,6 +233,7 @@ public class Compiler { List optimizations = new ArrayList<>(); optimizations.add(new PassNAddTypeConversionAssignment(program)); optimizations.add(new PassNAddNumberTypeConversions(program)); + optimizations.add(new PassNAddArrayNumberTypeConversions(program)); optimizations.add(new PassNDowngradeBytePlusWord(program)); optimizations.add(new PassNTypeInference(program)); optimizations.add(new PassNTypeIdSimplification(program)); @@ -253,8 +254,9 @@ public class Compiler { //optimizations.add(new Pass2ConstantAdditionElimination(program)); optimizations.add(new Pass2ConstantIfs(program)); optimizations.add(new Pass2ConstantStringConsolidation(program)); - optimizations.add(new Pass2FixInlineConstructors(program)); + //optimizations.add(new Pass2FixInlineConstructors(program)); optimizations.add(new Pass2FixInlineConstructorsNew(program)); + optimizations.add(new PassNAddTypeConversionAssignment(program)); optimizations.add(new Pass2TypeInference(program)); optimizations.add(new PassNEliminateUnusedVars(program, true)); optimizations.add(new Pass2EliminateRedundantCasts(program)); diff --git a/src/main/java/dk/camelot64/kickc/model/iterator/ProgramExpressionBinary.java b/src/main/java/dk/camelot64/kickc/model/iterator/ProgramExpressionBinary.java index ff1a8b9e0..f23553a71 100644 --- a/src/main/java/dk/camelot64/kickc/model/iterator/ProgramExpressionBinary.java +++ b/src/main/java/dk/camelot64/kickc/model/iterator/ProgramExpressionBinary.java @@ -169,7 +169,13 @@ public interface ProgramExpressionBinary extends ProgramExpression { if(assignment.getrValue1() == null && assignment.getOperator() == null) { assignment.setOperator(Operators.getCastUnary(toType)); } else { - throw new InternalError("Not implemented!"); + Scope blockScope = symbols.getScope(currentScope); + VariableIntermediate tmpVar = blockScope.addVariableIntermediate(); + SymbolType rightType = SymbolTypeInference.inferType(symbols, getRight()); + tmpVar.setType(rightType); + StatementAssignment newAssignment = new StatementAssignment(assignment.getlValue(), Operators.getCastUnary(toType), tmpVar.getRef(), assignment.getSource(), Comment.NO_COMMENTS); + assignment.setlValue(tmpVar.getRef()); + stmtIt.add(newAssignment); } } diff --git a/src/main/java/dk/camelot64/kickc/model/types/SymbolTypeInference.java b/src/main/java/dk/camelot64/kickc/model/types/SymbolTypeInference.java index d512f96e7..8944b1cfa 100644 --- a/src/main/java/dk/camelot64/kickc/model/types/SymbolTypeInference.java +++ b/src/main/java/dk/camelot64/kickc/model/types/SymbolTypeInference.java @@ -139,7 +139,13 @@ public class SymbolTypeInference { elmType = type; } else { if(!elmType.equals(type)) { - throw new RuntimeException("Array element has type mismatch " + elm.toString() + " not matching type " + elmType.getTypeName()); + if(SymbolType.NUMBER.equals(elmType) && SymbolType.isInteger(type)) { + elmType = SymbolType.NUMBER; + } else if(SymbolType.isInteger(elmType) && SymbolType.NUMBER.equals(type)) { + elmType = SymbolType.NUMBER; + } else { + throw new CompileError("Array element has type mismatch "+elm.toString() + " not matching type " + elmType.getTypeName()); + } } } } diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantRValueConsolidation.java b/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantRValueConsolidation.java index eed062e60..edf2a49c3 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantRValueConsolidation.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantRValueConsolidation.java @@ -98,7 +98,7 @@ public class Pass2ConstantRValueConsolidation extends Pass2SsaOptimization { } else { if(!listType.equals(elmType)) { // No overlap between list type and element type - throw new RuntimeException("Array type " + listType + " does not match element type" + elmType + ". Array: " + valueList.toString(getProgram())); + throw new RuntimeException("Array type " + listType + " does not match element type " + elmType + ". Array: " + valueList.toString(getProgram())); } } elements.add(constantValue); diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass2FixInlineConstructorsNew.java b/src/main/java/dk/camelot64/kickc/passes/Pass2FixInlineConstructorsNew.java index edd754321..e8c83cf29 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass2FixInlineConstructorsNew.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass2FixInlineConstructorsNew.java @@ -1,11 +1,15 @@ package dk.camelot64.kickc.passes; import dk.camelot64.kickc.model.Comment; +import dk.camelot64.kickc.model.ControlFlowBlock; import dk.camelot64.kickc.model.Program; +import dk.camelot64.kickc.model.iterator.ProgramExpression; import dk.camelot64.kickc.model.iterator.ProgramExpressionIterator; import dk.camelot64.kickc.model.iterator.ProgramExpressionUnary; import dk.camelot64.kickc.model.operators.OperatorBinary; +import dk.camelot64.kickc.model.operators.OperatorCastPtr; import dk.camelot64.kickc.model.operators.Operators; +import dk.camelot64.kickc.model.statements.Statement; import dk.camelot64.kickc.model.statements.StatementAssignment; import dk.camelot64.kickc.model.symbols.Scope; import dk.camelot64.kickc.model.symbols.VariableIntermediate; @@ -15,6 +19,7 @@ import dk.camelot64.kickc.model.values.RValue; import dk.camelot64.kickc.model.values.ValueList; import java.util.List; +import java.util.ListIterator; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -32,29 +37,19 @@ public class Pass2FixInlineConstructorsNew extends Pass2SsaOptimization { AtomicBoolean optimized = new AtomicBoolean(false); ProgramExpressionIterator.execute(getProgram(), (programExpression, currentStmt, stmtIt, currentBlock) -> { if(programExpression instanceof ProgramExpressionUnary) { - if(programExpression.getOperator().equals(Operators.CAST_WORD)) { - if(((ProgramExpressionUnary) programExpression).getOperand() instanceof ValueList) { - ValueList list = (ValueList) ((ProgramExpressionUnary) programExpression).getOperand(); - List listValues = list.getList(); - if(listValues.size()==2) { - - OperatorBinary constructOperator = Operators.WORD; - SymbolType constructType = SymbolType.WORD; - - // Convert list to a word constructor in a new tmp variable - Scope currentScope = Pass2FixInlineConstructorsNew.this.getScope().getScope(currentBlock.getScope()); - VariableIntermediate tmpVar = currentScope.addVariableIntermediate(); - tmpVar.setTypeInferred(constructType); - // Move backward - to insert before the current statement - stmtIt.previous(); - // Add assignment of the new tmpVar - StatementAssignment assignment = new StatementAssignment(tmpVar.getRef(), new CastValue(SymbolType.BYTE, listValues.get(0)), constructOperator, new CastValue(SymbolType.BYTE, listValues.get(1)), currentStmt.getSource(), Comment.NO_COMMENTS); - stmtIt.add(assignment); - // Move back before the current statement - stmtIt.next(); - // Replace current value with the reference - programExpression.set(tmpVar.getRef()); - Pass2FixInlineConstructorsNew.this.getLog().append("Fixing inline constructor with " + assignment.toString()); + if(((ProgramExpressionUnary) programExpression).getOperand() instanceof ValueList) { + ValueList list = (ValueList) ((ProgramExpressionUnary) programExpression).getOperand(); + List listValues = list.getList(); + if(listValues.size() == 2) { + if(programExpression.getOperator().equals(Operators.CAST_WORD)) { + addLiteralWordConstructor(Operators.WORD, SymbolType.WORD, SymbolType.BYTE, programExpression, listValues, currentStmt, stmtIt, currentBlock); + optimized.set(true); + } else if(programExpression.getOperator() instanceof OperatorCastPtr) { + SymbolType castType = ((OperatorCastPtr) programExpression.getOperator()).getToType(); + addLiteralWordConstructor(Operators.WORD, castType, SymbolType.BYTE, programExpression, listValues, currentStmt, stmtIt, currentBlock); + optimized.set(true); + } else if(programExpression.getOperator().equals(Operators.CAST_DWORD)) { + addLiteralWordConstructor(Operators.DWORD, SymbolType.DWORD, SymbolType.WORD, programExpression, listValues, currentStmt, stmtIt, currentBlock); optimized.set(true); } } @@ -65,5 +60,35 @@ public class Pass2FixInlineConstructorsNew extends Pass2SsaOptimization { } + /** + * Add a literal word/dword constructor. + * Converts a cast value-list with 2 items to a word/dword constructor. + * (word) { 1, 2 } -> 1 =w 2 + * @param constructOperator The operator to add + * @param constructType The type being constructed + * @param subType The sub-type + * @param programExpression The program expression containing the cast value list + * @param listValues + * @param currentStmt + * @param stmtIt + * @param currentBlock + */ + public void addLiteralWordConstructor(OperatorBinary constructOperator, SymbolType constructType, SymbolType subType, ProgramExpression programExpression, List listValues, Statement currentStmt, ListIterator stmtIt, ControlFlowBlock currentBlock) { + // Convert list to a word constructor in a new tmp variable + Scope currentScope = Pass2FixInlineConstructorsNew.this.getScope().getScope(currentBlock.getScope()); + VariableIntermediate tmpVar = currentScope.addVariableIntermediate(); + tmpVar.setTypeInferred(constructType); + // Move backward - to insert before the current statement + stmtIt.previous(); + // Add assignment of the new tmpVar + StatementAssignment assignment = new StatementAssignment(tmpVar.getRef(), new CastValue(subType, listValues.get(0)), constructOperator, new CastValue(subType, listValues.get(1)), currentStmt.getSource(), Comment.NO_COMMENTS); + stmtIt.add(assignment); + // Move back before the current statement + stmtIt.next(); + // Replace current value with the reference + programExpression.set(tmpVar.getRef()); + Pass2FixInlineConstructorsNew.this.getLog().append("Fixing inline constructor with " + assignment.toString()); + } + } diff --git a/src/main/java/dk/camelot64/kickc/passes/PassNAddArrayNumberTypeConversions.java b/src/main/java/dk/camelot64/kickc/passes/PassNAddArrayNumberTypeConversions.java new file mode 100644 index 000000000..fb3599b09 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/passes/PassNAddArrayNumberTypeConversions.java @@ -0,0 +1,63 @@ +package dk.camelot64.kickc.passes; + +import dk.camelot64.kickc.model.CompileError; +import dk.camelot64.kickc.model.Program; +import dk.camelot64.kickc.model.iterator.ProgramExpressionBinary; +import dk.camelot64.kickc.model.iterator.ProgramExpressionIterator; +import dk.camelot64.kickc.model.types.SymbolType; +import dk.camelot64.kickc.model.types.SymbolTypeArray; +import dk.camelot64.kickc.model.types.SymbolTypeConversion; +import dk.camelot64.kickc.model.types.SymbolTypeInference; +import dk.camelot64.kickc.model.values.*; + +import java.util.ListIterator; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Add casts to {@link SymbolType#NUMBER} expressions that are inside array initializers based on the type of the array + */ +public class PassNAddArrayNumberTypeConversions extends Pass2SsaOptimization { + + public PassNAddArrayNumberTypeConversions(Program program) { + super(program); + } + + @Override + public boolean step() { + AtomicBoolean modified = new AtomicBoolean(false); + ProgramExpressionIterator.execute(getProgram(), (binaryExpression, currentStmt, stmtIt, currentBlock) -> { + if(binaryExpression instanceof ProgramExpressionBinary) { + ProgramExpressionBinary binary = (ProgramExpressionBinary) binaryExpression; + RValue left = binary.getLeft(); + RValue right = binary.getRight(); + SymbolType leftType = SymbolTypeInference.inferType(getProgram().getScope(), left); + if(leftType instanceof SymbolTypeArray && right instanceof ValueList) { + SymbolType declaredElmType = ((SymbolTypeArray) leftType).getElementType(); + ListIterator elmIterator = ((ValueList) right).getList().listIterator(); + while(elmIterator.hasNext()) { + RValue elm = elmIterator.next(); + SymbolType elmType = SymbolTypeInference.inferType(getProgram().getScope(), elm); + if(SymbolType.NUMBER.equals(elmType)) { + // Add a cast - if the value fits. + if(!SymbolTypeConversion.assignmentTypeMatch(declaredElmType, elmType)) { + throw new CompileError("Array type " + declaredElmType + " does not match element type " + elmType , currentStmt); + } + // TODO: Test if the value matches the declared type! + if(elm instanceof ConstantValue) { + elmIterator.set(new ConstantCastValue(declaredElmType, (ConstantValue) elm)); + } else { + elmIterator.set(new CastValue(declaredElmType, elm)); + } + modified.set(true); + } + } + if(modified.get()) { + getLog().append("Adding number conversion cast (" + declaredElmType+ ") to elements in " + (currentStmt == null ? "" : currentStmt.toString(getProgram(), false))); + } + } + } + }); + return modified.get(); + } + +} diff --git a/src/main/java/dk/camelot64/kickc/passes/PassNAddTypeConversionAssignment.java b/src/main/java/dk/camelot64/kickc/passes/PassNAddTypeConversionAssignment.java index 908cb0587..3fde76260 100644 --- a/src/main/java/dk/camelot64/kickc/passes/PassNAddTypeConversionAssignment.java +++ b/src/main/java/dk/camelot64/kickc/passes/PassNAddTypeConversionAssignment.java @@ -43,7 +43,22 @@ public class PassNAddTypeConversionAssignment extends Pass2SsaOptimization { if(leftType.equals(SymbolType.WORD) && isLiteralWordCandidate(rightType)) { SymbolType conversionType = SymbolType.WORD; getLog().append("Identified literal word (" + conversionType + ") " + binary.getRight().toString() + " in " + (currentStmt == null ? "" : currentStmt.toString(getProgram(), false))); - binary.addRightCast(SymbolType.WORD, stmtIt, currentBlock == null ? null : currentBlock.getScope(), getScope()); + binary.addRightCast(conversionType, stmtIt, currentBlock == null ? null : currentBlock.getScope(), getScope()); + modified.set(true); + } + // Detect word literal constructor + if(leftType instanceof SymbolTypePointer && isLiteralWordCandidate(rightType)) { + SymbolType conversionType = SymbolType.WORD; + getLog().append("Identified literal word (" + conversionType + ") " + binary.getRight().toString() + " in " + (currentStmt == null ? "" : currentStmt.toString(getProgram(), false))); + binary.addRightCast(conversionType, stmtIt, currentBlock == null ? null : currentBlock.getScope(), getScope()); + modified.set(true); + } + + // Detect dword literal constructor + if(leftType.equals(SymbolType.DWORD) && isLiteralWordCandidate(rightType)) { + SymbolType conversionType = SymbolType.DWORD; + getLog().append("Identified literal word (" + conversionType + ") " + binary.getRight().toString() + " in " + (currentStmt == null ? "" : currentStmt.toString(getProgram(), false))); + binary.addRightCast(conversionType, stmtIt, currentBlock == null ? null : currentBlock.getScope(), getScope()); modified.set(true); } @@ -67,7 +82,7 @@ public class PassNAddTypeConversionAssignment extends Pass2SsaOptimization { if(rightType instanceof SymbolTypeArray) { SymbolTypeArray rightArray = (SymbolTypeArray) rightType; if(new ConstantInteger(2L, SymbolType.BYTE).equals(rightArray.getSize())) - if(SymbolType.BYTE.equals(rightArray.getElementType()) || SymbolType.NUMBER.equals(rightArray.getElementType())) + if(SymbolType.isInteger(rightArray.getElementType())) return true; } return false; diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 3bb2350d3..ca909666e 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -32,9 +32,29 @@ public class TestPrograms { public TestPrograms() { } + @Test + public void testMixedArray0() throws IOException, URISyntaxException { + compileAndCompare("mixed-array-0"); + } + + @Test + public void testInlinePointer2() throws IOException, URISyntaxException { + compileAndCompare("inline-pointer-2"); + } + + @Test + public void testInlinePointer1() throws IOException, URISyntaxException { + compileAndCompare("inline-pointer-1"); + } + + @Test + public void testInlinePointer0() throws IOException, URISyntaxException { + compileAndCompare("inline-pointer-0"); + } + @Test public void testToD018Problem() throws IOException, URISyntaxException { - compileAndCompare("tod018-problem", log()); + compileAndCompare("tod018-problem"); } @Test diff --git a/src/test/kc/inline-pointer-0.kc b/src/test/kc/inline-pointer-0.kc new file mode 100644 index 000000000..6f60ecd49 --- /dev/null +++ b/src/test/kc/inline-pointer-0.kc @@ -0,0 +1,6 @@ +// Tests creating a literal pointer from two bytes + +void main() { + byte* screen = (byte*) { 4, 0 }; + *screen = 'a'; +} \ No newline at end of file diff --git a/src/test/kc/inline-pointer-1.kc b/src/test/kc/inline-pointer-1.kc new file mode 100644 index 000000000..bb1523bbc --- /dev/null +++ b/src/test/kc/inline-pointer-1.kc @@ -0,0 +1,11 @@ +// Tests creating a literal pointer from two bytes + +void main() { + puta(4, 0x00); + puta(5, 0x18); +} + +void puta(byte ph, byte pl) { + byte* screen = (byte*) { ph, pl }; + *screen = 'a'; +} \ No newline at end of file diff --git a/src/test/kc/inline-pointer-2.kc b/src/test/kc/inline-pointer-2.kc new file mode 100644 index 000000000..7bfdfdfe6 --- /dev/null +++ b/src/test/kc/inline-pointer-2.kc @@ -0,0 +1,6 @@ +// Tests creating a literal pointer from two bytes + +void main() { + byte* screen = (byte*){ 4, 0 } + (word){ 0, 40 }; + *screen = 'a'; +} \ No newline at end of file diff --git a/src/test/kc/mixed-array-0.kc b/src/test/kc/mixed-array-0.kc new file mode 100644 index 000000000..7065854c7 --- /dev/null +++ b/src/test/kc/mixed-array-0.kc @@ -0,0 +1,9 @@ +// Test an array with mixed byte/number types + +void main() { + byte[] msg = { 1ub, 2, 3 }; + byte* SCREEN = 0x400; + SCREEN[0] = msg[0]; + SCREEN[1] = msg[1]; + SCREEN[2] = msg[2]; +} \ No newline at end of file diff --git a/src/test/kc/true-inline-words.kc b/src/test/kc/true-inline-words.kc index b9d96a0b6..cc701f1e0 100644 --- a/src/test/kc/true-inline-words.kc +++ b/src/test/kc/true-inline-words.kc @@ -3,7 +3,7 @@ void main() { byte[] bs = { 'c', 'm' }; // constant byte array byte b = 4; // constant byte word w = { b, 0 }; // constant inline word - word w2 = { 1, 1 } + w + { 0, 0 }; // constant inline words inside expression + word w2 = (word){ 1, 1 } + w + (word){ 0, 0 }; // constant inline words inside expression byte* sc = w2; // implicit cast to (byte*) *sc = bs[1]; // In the end $501 is set to 'c'