diff --git a/.classpath b/.classpath index acfe841..ebef12c 100644 --- a/.classpath +++ b/.classpath @@ -5,7 +5,7 @@ - + diff --git a/.project b/.project index 7e77cd0..95e489d 100644 --- a/.project +++ b/.project @@ -1,6 +1,6 @@ - Javali-HW4 + Javali-HWB diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..0c68a61 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/Grade.txt b/Grade.txt index 2432205..4dc5694 100644 --- a/Grade.txt +++ b/Grade.txt @@ -1,39 +1,15 @@ -nop90: 29/30 points - -Comments: -I found that the following tasks were not implemented correctly or contained errors. - -* Null pointers check doesn't work for methods: - " - class Main { - void main() { - A a; - a.m(); - } - } - class A { - void m() { } - } - " -* Side effects - If methods have side effects, then the execution order matters: - " - class Main { - void main() { - write(m1() + m2()); - writeln(); - } - - int m1() { - write(1); - return 1; - } - - int m2() { - write(2); - return 2; - } - } - " -* Can't compile programs which use many registers - +team name: nop90 +task 1 correctness: 95 +task 1 score: 541.3 +task 1 points (max 25 points): 16 +task 2 deadline 1 correctness: 100 +task 2 deadline 1 score: 14.1 +task 2 deadline 1 points: 1 +task 2 deadline 2 correctness: 97 +task 2 deadline 2 score: 264.5 +task 2 deadline 2 points: 15 +task 2 runtimeOpts correctness: 98 +task 2 runtimeOpts score: 265.3 +task 2 runtimeOpts bonus points: 0 +task 2 points (max 25 points): 15 +HWB TOTAL (max 50 points): 31 \ No newline at end of file diff --git a/Javali tests.launch b/Javali tests.launch new file mode 100644 index 0000000..cb8a176 --- /dev/null +++ b/Javali tests.launch @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md deleted file mode 100644 index 284ef07..0000000 --- a/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# HW4: code generation -Better viewed as markdown - -# VTables -They are generated when each class is visited, except for those of Object, -Object[], int[] and boolean[], which are generated in the bootstrapping. - -When the object vtable is generated, so is the array table, which just -contains a reference to the supertype (in our case `Object`) and an unused -pointer to the declared type of the element's vtable. - - supertype pointer (Object) <-- vtable pointer - element vtable pointer (unused) - -Each type's vtable contains the supertype as the first element (trait shared -with array type's vtables for easier cast type comparisons). Then they -contain all the methods available to an object of that type, including -all those inherited. As the dynamic type is not known, the number and -position of the methods is not known when executing a method, therefore -each method's pointer is prepended by the method's name hash (similar to -fields in an object). - - supertype pointer <-- vtable pointer - hash("method0") - method0 pointer - hash("method1") - method1 pointer - ... - hash("methodN") - methodN pointer - -# Method calls - -The basic scheme used for method calls and the structure of the stack -frame is that described in the `__cdecl` used in GCC. In short, this is -the execution of a method call: - -1. The caller pushes the arguments in the stack, from right-to-left, -leaving the 0th argument (the reference to `this`) at the top of the stack. -2. The caller executes `call` (the instruction pointer gets stored in the stack) -3. The callee now takes control, and its first action is to save the base -pointer to the stack and set the base pointer to the current stack pointer. -Therefore, the arguments and return address are available to the method. -4. The local variables are reserved and zeroed in the stack. -5. The CALLEE_SAVED registers are saved to the stack. -6. The method executes its code -7. The CALLEE_SAVED registers are restored to their original status. -8. The callee restores the stack and base registers to its initial statuses. -This is accomplished with a `leave` instruction. Then it returns using `ret`. -This removes the instruction pointer from the stack leaving only the arguments. -9. The caller finally removes all the arguments from the stack and obtains the -return value from EAX. - -Therefore, the stack frame follows the following structure (N arguments -and M local variables): - - top of stack <-- ESP - ... - saved registers - localM <-- EBP - 4 - M * 4 - ... - local1 <-- EBP - 8 - local0 <-- EBP - 4 - Old EBP <-- EBP - Return EIP - this <-- EBP + 8 - arg0 <-- EBP + 12 - ... - argN <-- EBP + 12 + N * 4 - -# Variable and object representation -## Primitive variables -They are represented by value, with both integers and booleans occupying -4 bytes, and booleans being represented as TRUE = 0, FALSE = 1. - -## Reference variables -Stored in the heap, using calloc (to zero-initialize them). - -### Arrays -On top of storing the array elements, some overhead is stored with it, -namely a pointer to the array vtable for casts, and the size of the -array to check the bounds of the array on accesses or assignments. -The pointer to the array points to the vtable pointer (the first -element in the object structure): - - array vtable pointer <-- var pointer - array size - element0 - element1 - ... - elementN - -### Objects - -Stores a pointer to the object's type vtable and all the fields. As the -dynamic type of the object cannot be determined and therefore the fields -not statically ordered, each field occupies 8 bytes, with the first 4 containing -the hash for the VariableSymbol that represents it and the others the -value or reference to the field. - -The hash allows hiding variables, but introduces an overhead when accessing -a field, to look for the position in which it is located. - - vtable pointer <-- var pointer - field0's hash - field0 - field1's hash - field1 - ... - fieldN's hash - fieldN diff --git a/benchmarks/HWB/Quicksort.javali b/benchmarks/HWB/Quicksort.javali new file mode 100644 index 0000000..85532ee --- /dev/null +++ b/benchmarks/HWB/Quicksort.javali @@ -0,0 +1,60 @@ +// Overall test of arrays, loops, etc. that does a simple quicksort. +class Record { + int a ; +} + +class Main { + Record [] a; + int i; + + void swap(Record r1, Record r2) { + int temp; + temp = 0+1; + temp = r1.a; + r1.a = r2.a; + r2.a = temp; + } + + void sort(int left, int right) { + int i,j; + int m; + + m = (a[left].a + a[right].a) / (1 + 1); + i = left; + j = right; + while (i <= j) { + while (a[i].a < m) { i = i+1; } + while (a[j].a > m) { j = j-1; } + if (i <= j) { + swap(a[i], a[j]); + i = i+1; + j = j-1; + } + } + if (left < j) { sort(left,j); } + if (i < right) { sort(i,right); } + } + + void main() { + int SIZE; + int j; + + SIZE = 10; + + a = new Record[SIZE * 1]; + j = 0; + while (j < SIZE) { + a[j] = new Record(); + a[j].a = read(); + j = j + 1; + } + sort(0, SIZE-1); + j = 0; + while (j < SIZE) { + i = a[j].a; + write(i); + writeln(); + j = j + 1; + } + } +} diff --git a/benchmarks/HWB/Quicksort.javali.in b/benchmarks/HWB/Quicksort.javali.in new file mode 100644 index 0000000..bb9b11e --- /dev/null +++ b/benchmarks/HWB/Quicksort.javali.in @@ -0,0 +1,10 @@ +1 +5 +6 +7 +8 +5 +2 +4 +6 +3 \ No newline at end of file diff --git a/benchmarks/HWB_nop90/AFewConstants.javali b/benchmarks/HWB_nop90/AFewConstants.javali new file mode 100644 index 0000000..97ad130 --- /dev/null +++ b/benchmarks/HWB_nop90/AFewConstants.javali @@ -0,0 +1,40 @@ +class Main { + int SIZE; + + void main() { + int[] a; + int i; + int pi; + int e; + int thou; + + pi = 3141; + e = 2718; + + a = readStuff(); + + i = 0; + while(i < SIZE) { + write(pi * e * a[i] / (1000 * 1000)); + writeln(); + write(a[i] * pi * e / (1000 * 1000)); + writeln(); + i = i + 1; + } + } + + int[] readStuff() { + int[] a; + int i; + + SIZE = 5; + a = new int[SIZE]; + + i = 0; + while (i < SIZE) { + a[i] = read(); + i = i + 1; + } + return a; + } +} \ No newline at end of file diff --git a/benchmarks/HWB_nop90/AFewConstants.javali.in b/benchmarks/HWB_nop90/AFewConstants.javali.in new file mode 100644 index 0000000..e811ab2 --- /dev/null +++ b/benchmarks/HWB_nop90/AFewConstants.javali.in @@ -0,0 +1,5 @@ +42 +1337 +9000 +12345678 +777 diff --git a/benchmarks/HWB_nop90/Unused.javali b/benchmarks/HWB_nop90/Unused.javali new file mode 100644 index 0000000..9d20ae5 --- /dev/null +++ b/benchmarks/HWB_nop90/Unused.javali @@ -0,0 +1,13 @@ +class Main { + void main() { + int i; + int j; + i = read(); + j = 2 * i; + + i = (i + 31) / 2; + j = (i * j) / 3; + + write(i); + } +} \ No newline at end of file diff --git a/benchmarks/HWB_nop90/Unused.javali.in b/benchmarks/HWB_nop90/Unused.javali.in new file mode 100644 index 0000000..920a139 --- /dev/null +++ b/benchmarks/HWB_nop90/Unused.javali.in @@ -0,0 +1 @@ +43 diff --git a/build.xml b/build.xml index c863af9..3148085 100644 --- a/build.xml +++ b/build.xml @@ -8,33 +8,40 @@ + + - + + + + + - - + + + - - - - - - - - - - - + + + + + + + + + + + @@ -44,28 +51,53 @@ - + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/javali_tests/HW1_nop90/Division.javali b/javali_tests/HW1_nop90/Division.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW1_nop90/EightVariablesWrite.javali b/javali_tests/HW1_nop90/EightVariablesWrite.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW1_nop90/Multiplication.javali b/javali_tests/HW1_nop90/Multiplication.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW1_nop90/Overflow.javali b/javali_tests/HW1_nop90/Overflow.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW1_nop90/Overflow.javali.in b/javali_tests/HW1_nop90/Overflow.javali.in old mode 100755 new mode 100644 diff --git a/javali_tests/HW1_nop90/ReadWrite.javali b/javali_tests/HW1_nop90/ReadWrite.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW1_nop90/ReadWrite.javali.in b/javali_tests/HW1_nop90/ReadWrite.javali.in old mode 100755 new mode 100644 diff --git a/javali_tests/HW1_nop90/UnaryOperators.javali b/javali_tests/HW1_nop90/UnaryOperators.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW1_nop90/noParentheses.javali b/javali_tests/HW1_nop90/noParentheses.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/Casts.javali b/javali_tests/HW2_nop90/Casts.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/Division.javali b/javali_tests/HW2_nop90/Division.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/EightVariablesWrite.javali b/javali_tests/HW2_nop90/EightVariablesWrite.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/Multiplication.javali b/javali_tests/HW2_nop90/Multiplication.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/NULLTest.javali b/javali_tests/HW2_nop90/NULLTest.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/Overflow.javali b/javali_tests/HW2_nop90/Overflow.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/Overflow.javali.in b/javali_tests/HW2_nop90/Overflow.javali.in old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/ReadWrite.javali b/javali_tests/HW2_nop90/ReadWrite.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/ReadWrite.javali.in b/javali_tests/HW2_nop90/ReadWrite.javali.in old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/UnaryOperators.javali b/javali_tests/HW2_nop90/UnaryOperators.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/conditionExpressions.javali b/javali_tests/HW2_nop90/conditionExpressions.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/invalidCasts.javali b/javali_tests/HW2_nop90/invalidCasts.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW2_nop90/noParentheses.javali b/javali_tests/HW2_nop90/noParentheses.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4/OkDifferentReads.javali b/javali_tests/HW4/OkDifferentReads.javali index f1f5140..291fb42 100644 --- a/javali_tests/HW4/OkDifferentReads.javali +++ b/javali_tests/HW4/OkDifferentReads.javali @@ -24,5 +24,3 @@ class Main { } } - - diff --git a/javali_tests/HW4_nop90/Booleans/OkEquals2.javali b/javali_tests/HW4_nop90/Booleans/OkEquals2.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Booleans/OkEquals3.javali b/javali_tests/HW4_nop90/Booleans/OkEquals3.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Casts/ErrCastUnrelatedType.javali b/javali_tests/HW4_nop90/Casts/ErrCastUnrelatedType.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Casts/ErrObjectToArrayCast.javali b/javali_tests/HW4_nop90/Casts/ErrObjectToArrayCast.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Casts/ErrPrimitiveCast.javali b/javali_tests/HW4_nop90/Casts/ErrPrimitiveCast.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Casts/ErrPrimitiveCast2.javali b/javali_tests/HW4_nop90/Casts/ErrPrimitiveCast2.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Casts/OkArrayToObjectCast.javali b/javali_tests/HW4_nop90/Casts/OkArrayToObjectCast.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Casts/OkArrayToObjectCast2.javali b/javali_tests/HW4_nop90/Casts/OkArrayToObjectCast2.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Casts/OkSubtype.javali b/javali_tests/HW4_nop90/Casts/OkSubtype.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Casts/OkTypeCast.javali b/javali_tests/HW4_nop90/Casts/OkTypeCast.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Casts/OkTypeToObjectCast.javali b/javali_tests/HW4_nop90/Casts/OkTypeToObjectCast.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/EightVariablesWrite.javali b/javali_tests/HW4_nop90/EightVariablesWrite.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/ErrDivisionByZero.javali b/javali_tests/HW4_nop90/ErrDivisionByZero.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/Fields/OkObjectFields.javali b/javali_tests/HW4_nop90/Fields/OkObjectFields.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/OkAssignments.javali.in b/javali_tests/HW4_nop90/OkAssignments.javali.in index 56a6051..7813681 100644 --- a/javali_tests/HW4_nop90/OkAssignments.javali.in +++ b/javali_tests/HW4_nop90/OkAssignments.javali.in @@ -1 +1 @@ -1 \ No newline at end of file +5 \ No newline at end of file diff --git a/javali_tests/HW4_nop90/OkBoolBinaryOp.javali b/javali_tests/HW4_nop90/OkBoolBinaryOp.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/OkCasts.javali b/javali_tests/HW4_nop90/OkCasts.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/OkFalseInitialized.javali b/javali_tests/HW4_nop90/OkFalseInitialized.javali new file mode 100644 index 0000000..1881d67 --- /dev/null +++ b/javali_tests/HW4_nop90/OkFalseInitialized.javali @@ -0,0 +1,10 @@ +/* Test that variables are zero initialized */ + +class Main { + void main() { + boolean a; + if (!a){ + write(5); } + } +} + diff --git a/javali_tests/HW4_nop90/OkNullAssignment.javali b/javali_tests/HW4_nop90/OkNullAssignment.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/OkRegisterUse.javali b/javali_tests/HW4_nop90/OkRegisterUse.javali index 3c7e635..8077759 100644 --- a/javali_tests/HW4_nop90/OkRegisterUse.javali +++ b/javali_tests/HW4_nop90/OkRegisterUse.javali @@ -8,8 +8,7 @@ class Main { e = true; f = true; g = true; - z = 0; - while (z < 30) { + while (e) { if (f){ if (g){ u = 5; @@ -22,8 +21,6 @@ class Main { else { j = true; } - z = z + 1; - } } diff --git a/javali_tests/HW4_nop90/OkUnaryOperators.javali b/javali_tests/HW4_nop90/OkUnaryOperators.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/OkVariables.javali b/javali_tests/HW4_nop90/OkVariables.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/OkZeroInitialized.javali b/javali_tests/HW4_nop90/OkZeroInitialized.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/ReturnTests/OkReturnObject.javali b/javali_tests/HW4_nop90/ReturnTests/OkReturnObject.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/method invocation/OkCallByValue.javali b/javali_tests/HW4_nop90/method invocation/OkCallByValue.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/method invocation/OkCallByValue2.javali b/javali_tests/HW4_nop90/method invocation/OkCallByValue2.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/method invocation/OkCallByValue3.javali b/javali_tests/HW4_nop90/method invocation/OkCallByValue3.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/method invocation/OkCallInheritedMethod.javali b/javali_tests/HW4_nop90/method invocation/OkCallInheritedMethod.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/method invocation/OkMethod.javali b/javali_tests/HW4_nop90/method invocation/OkMethod.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HW4_nop90/method invocation/OkMethod2.javali b/javali_tests/HW4_nop90/method invocation/OkMethod2.javali old mode 100755 new mode 100644 diff --git a/javali_tests/HWB/Quicksort.javali b/javali_tests/HWB/Quicksort.javali new file mode 100644 index 0000000..85532ee --- /dev/null +++ b/javali_tests/HWB/Quicksort.javali @@ -0,0 +1,60 @@ +// Overall test of arrays, loops, etc. that does a simple quicksort. +class Record { + int a ; +} + +class Main { + Record [] a; + int i; + + void swap(Record r1, Record r2) { + int temp; + temp = 0+1; + temp = r1.a; + r1.a = r2.a; + r2.a = temp; + } + + void sort(int left, int right) { + int i,j; + int m; + + m = (a[left].a + a[right].a) / (1 + 1); + i = left; + j = right; + while (i <= j) { + while (a[i].a < m) { i = i+1; } + while (a[j].a > m) { j = j-1; } + if (i <= j) { + swap(a[i], a[j]); + i = i+1; + j = j-1; + } + } + if (left < j) { sort(left,j); } + if (i < right) { sort(i,right); } + } + + void main() { + int SIZE; + int j; + + SIZE = 10; + + a = new Record[SIZE * 1]; + j = 0; + while (j < SIZE) { + a[j] = new Record(); + a[j].a = read(); + j = j + 1; + } + sort(0, SIZE-1); + j = 0; + while (j < SIZE) { + i = a[j].a; + write(i); + writeln(); + j = j + 1; + } + } +} diff --git a/javali_tests/HWB/Quicksort.javali.in b/javali_tests/HWB/Quicksort.javali.in new file mode 100644 index 0000000..bb9b11e --- /dev/null +++ b/javali_tests/HWB/Quicksort.javali.in @@ -0,0 +1,10 @@ +1 +5 +6 +7 +8 +5 +2 +4 +6 +3 \ No newline at end of file diff --git a/src/cd/Config.java b/src/cd/Config.java index db99934..ac7130d 100644 --- a/src/cd/Config.java +++ b/src/cd/Config.java @@ -3,137 +3,160 @@ package cd; import java.io.File; public class Config { - - public static enum SystemKind { - LINUX, - WINDOWS, - MACOSX - } - - /** - * Defines the extension used for assembler files on this platform. - * Currently always {@code .s}. - */ - public static final String ASMEXT = ".s"; - - /** Defines the extension used for binary files on this platform. */ - public static final String BINARYEXT; - - /** Defines the name of the main function to be used in .s file */ - public static final String MAIN; - - /** Defines the name of the printf function to be used in .s file */ - public static final String PRINTF; - - /** Defines the name of the scanf function to be used in .s file */ - public static final String SCANF; - - /** Defines the name of the calloc function to be used in .s file */ - public static final String CALLOC; - - /** Defines the name of the exit function to be used in .s file */ - public static final String EXIT; - - /** The assembler directive used to define a constant string */ - public static final String DOT_STRING; - - /** The assembler directive used to define a constant int */ - public static final String DOT_INT; - - /** The assembler directive used to start the text section */ - public static final String TEXT_SECTION; - - /** The assembler directive used to start the section for integer data */ - public static final String DATA_INT_SECTION; - - /** - * The assembler directive used to start the section for string data (if - * different from {@link #DATA_INT_SECTION} - */ - public static final String DATA_STR_SECTION; - - /** Comment separator used in assembly files */ - public static final String COMMENT_SEP; - - /** - * Defines the assembler command to use. Should be a string array where each - * entry is one argument. Use the special string "$0" to refer to the output - * file, and $1 to refer to the ".s" file. - */ - public static final String[] ASM; - - /** - * The directory from which to run the assembler. In a CYGWIN installation, - * this can make a big difference! - */ - public static final File ASM_DIR; - - /** - * sizeof a pointer in bytes in the target platform. - */ - public static final int SIZEOF_PTR = 4; - /** - * Name of java executable in JRE path - */ + public static enum SystemKind { + LINUX, + WINDOWS, + MACOSX + } + + public static final int TRUE = 1; + public static final int FALSE = 0; + + /** + * Defines the extension used for assembler files on this platform. + * Currently always {@code .s}. + */ + public static final String ASMEXT = ".s"; + + /** + * Defines the extension used for binary files on this platform. + */ + public static final String BINARYEXT; + + /** + * Defines the name of the main function to be used in .s file + */ + public static final String MAIN; + + /** + * Defines the name of the printf function to be used in .s file + */ + public static final String PRINTF; + + /** + * Defines the name of the scanf function to be used in .s file + */ + public static final String SCANF; + + /** + * Defines the name of the calloc function to be used in .s file + */ + public static final String CALLOC; + + /** + * Defines the name of the exit function to be used in .s file + */ + public static final String EXIT; + + /** + * The assembler directive used to define a constant string + */ + public static final String DOT_STRING; + + /** + * The assembler directive used to define a constant int + */ + public static final String DOT_INT; + + /** + * The assembler directive used to start the text section + */ + public static final String TEXT_SECTION; + + /** + * The assembler directive used to start the section for integer data + */ + public static final String DATA_INT_SECTION; + + /** + * The assembler directive used to start the section for string data (if + * different from {@link #DATA_INT_SECTION} + */ + public static final String DATA_STR_SECTION; + + /** + * Comment separator used in assembly files + */ + public static final String COMMENT_SEP; + + /** + * Defines the assembler command to use. Should be a string array where each + * entry is one argument. Use the special string "$0" to refer to the output + * file, and $1 to refer to the ".s" file. + */ + public static final String[] ASM; + + /** + * The directory from which to run the assembler. In a CYGWIN installation, + * this can make a big difference! + */ + public static final File ASM_DIR; + + /** + * sizeof a pointer in bytes in the target platform. + */ + public static final int SIZEOF_PTR = 4; + + /** + * Name of java executable in JRE path + */ public static final String JAVA_EXE; - - static { - final String os = System.getProperty("os.name").toLowerCase(); - - if(os.contains("windows") || os.contains("nt")) { - BINARYEXT = ".exe"; - MAIN = "_main"; - PRINTF = "_printf"; - SCANF = "_scanf"; - CALLOC = "_calloc"; - EXIT = "_exit"; - // These are set up for a Cygwin installation on C:, - // you can change as needed. - ASM = new String[]{"gcc", "-o", "$0", "$1"}; - ASM_DIR = new File("C:\\CYGWIN\\BIN"); - JAVA_EXE = "javaw.exe"; - DOT_STRING = ".string"; - DOT_INT = ".int"; - TEXT_SECTION = ".section .text"; - DATA_INT_SECTION = ".section .data"; - DATA_STR_SECTION = ".section .data"; - COMMENT_SEP = "#"; - } - else if(os.contains("mac os x") || os.contains("darwin")) { - BINARYEXT = ".bin"; - MAIN = "_main"; - PRINTF = "_printf"; - SCANF = "_scanf"; - CALLOC = "_calloc"; - EXIT = "_exit"; - ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"}; - ASM_DIR = new File("."); - JAVA_EXE = "java"; - DOT_STRING = ".asciz"; - DOT_INT = ".long"; - TEXT_SECTION = ".text"; - DATA_INT_SECTION = ".data"; - DATA_STR_SECTION = ".cstring"; - COMMENT_SEP = "#"; - } - else { - BINARYEXT = ".bin"; - MAIN = "main"; - PRINTF = "printf"; - SCANF = "scanf"; - CALLOC = "calloc"; - EXIT = "exit"; - ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"}; - ASM_DIR = new File("."); - JAVA_EXE = "java"; - DOT_STRING = ".string"; - DOT_INT = ".int"; - TEXT_SECTION = ".section .text"; - DATA_INT_SECTION = ".section .data"; - DATA_STR_SECTION = ".section .data"; - COMMENT_SEP = "#"; - } - } - + + static { + final String os = System.getProperty("os.name").toLowerCase(); + + if (os.contains("windows") || os.contains("nt")) { + BINARYEXT = ".exe"; + MAIN = "_main"; + PRINTF = "_printf"; + SCANF = "_scanf"; + CALLOC = "_calloc"; + EXIT = "_exit"; + // These are set up for a Cygwin installation on C:, + // you can change as needed. + ASM = new String[]{"gcc", "-o", "$0", "$1"}; + ASM_DIR = new File("C:\\CYGWIN\\BIN"); + JAVA_EXE = "javaw.exe"; + DOT_STRING = ".string"; + DOT_INT = ".int"; + TEXT_SECTION = ".section .text"; + DATA_INT_SECTION = ".section .data"; + DATA_STR_SECTION = ".section .data"; + COMMENT_SEP = "#"; + } else if (os.contains("mac os x") || os.contains("darwin")) { + BINARYEXT = ".bin"; + MAIN = "_main"; + PRINTF = "_printf"; + SCANF = "_scanf"; + CALLOC = "_calloc"; + EXIT = "_exit"; + ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"}; + ASM_DIR = new File("."); + JAVA_EXE = "java"; + DOT_STRING = ".asciz"; + DOT_INT = ".long"; + TEXT_SECTION = ".text"; + DATA_INT_SECTION = ".data"; + DATA_STR_SECTION = ".cstring"; + COMMENT_SEP = "#"; + } else { + BINARYEXT = ".bin"; + MAIN = "main"; + PRINTF = "printf"; + SCANF = "scanf"; + CALLOC = "calloc"; + EXIT = "exit"; + ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"}; + ASM_DIR = new File("."); + JAVA_EXE = "java"; + DOT_STRING = ".string"; + DOT_INT = ".int"; + TEXT_SECTION = ".section .text"; + DATA_INT_SECTION = ".section .data"; + DATA_STR_SECTION = ".section .data"; + COMMENT_SEP = "#"; + } + } + } diff --git a/src/cd/Main.java b/src/cd/Main.java index 335c48a..8c8a508 100644 --- a/src/cd/Main.java +++ b/src/cd/Main.java @@ -1,6 +1,6 @@ package cd; -import cd.backend.codegen.AstCodeGenerator; +import cd.backend.codegen.CfgCodeGenerator; import cd.frontend.parser.JavaliAstVisitor; import cd.frontend.parser.JavaliLexer; import cd.frontend.parser.JavaliParser; @@ -8,9 +8,13 @@ import cd.frontend.parser.JavaliParser.UnitContext; import cd.frontend.parser.ParseFailure; import cd.frontend.semantic.SemanticAnalyzer; import cd.ir.Ast.ClassDecl; -import cd.ir.Symbol; +import cd.ir.Ast.MethodDecl; +import cd.ir.Symbol.ClassSymbol; import cd.ir.Symbol.TypeSymbol; +import cd.transform.AstOptimizer; +import cd.transform.CfgBuilder; import cd.util.debug.AstDump; +import cd.util.debug.CfgDump; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.BailErrorStrategy; import org.antlr.v4.runtime.CommonTokenStream; @@ -20,26 +24,29 @@ import java.io.*; import java.util.ArrayList; import java.util.List; -/** - * The main entrypoint for the compiler. Consists of a series - * of routines which must be invoked in order. The main() - * routine here invokes these routines, as does the unit testing - * code. This is not the best programming practice, as the - * series of calls to be invoked is duplicated in two places in the - * code, but it will do for now. */ +/** + * The main entrypoint for the compiler. Consists of a series of routines which must be invoked in + * order. The main() routine here invokes these routines, as does the unit testing code. This is not + * the best programming practice, as the series of calls to be invoked is duplicated in two + * places in the code, but it will do for now. + */ public class Main { - + // Set to non-null to write debug info out public Writer debug = null; - + // Set to non-null to write dump of control flow graph public File cfgdumpbase; - - /** Symbol for the Main type */ - public Symbol.ClassSymbol mainType; - - /** List of all type symbols, used by code generator. */ - public List allTypeSymbols; + + /** + * Symbol for the Main type + */ + public ClassSymbol mainType; + + /** + * List of all type symbols, used by code generator. + */ + public List allTypeSymbols; public void debug(String format, Object... args) { if (debug != null) { @@ -53,23 +60,28 @@ public class Main { } } } - - /** Parse command line, invoke compile() routine */ + + /** + * Parse command line, invoke compile() routine + */ public static void main(String args[]) throws IOException { Main m = new Main(); - + for (String arg : args) { if (arg.equals("-d")) m.debug = new OutputStreamWriter(System.err); else { + if (m.debug != null) + m.cfgdumpbase = new File(arg); + FileReader fin = new FileReader(arg); // Parse: List astRoots = m.parse(fin); - + // Run the semantic check: m.semanticCheck(astRoots); - + // Generate code: String sFile = arg + Config.ASMEXT; try (FileWriter fout = new FileWriter(sFile)) { @@ -78,51 +90,63 @@ public class Main { } } } - - - /** Parses an input stream into an AST - * @throws IOException */ + + /** + * Parses an input stream into an AST + * + * @throws IOException + */ public List parse(Reader reader) throws IOException { List result = new ArrayList(); - + try { JavaliLexer lexer = new JavaliLexer(new ANTLRInputStream(reader)); JavaliParser parser = new JavaliParser(new CommonTokenStream(lexer)); parser.setErrorHandler(new BailErrorStrategy()); UnitContext unit = parser.unit(); - + JavaliAstVisitor visitor = new JavaliAstVisitor(); visitor.visit(unit); - result = visitor.classDecls; + result = visitor.classDecls; } catch (ParseCancellationException e) { ParseFailure pf = new ParseFailure(0, "?"); pf.initCause(e); throw pf; } - + debug("AST Resulting From Parsing Stage:"); dumpAst(result); - + return result; } - - + + // TODO: decide how to do/call optimization public void semanticCheck(List astRoots) { - { - new SemanticAnalyzer(this).check(astRoots); - } - } - - public void generateCode(List astRoots, Writer out) { - { - AstCodeGenerator cg = AstCodeGenerator.createCodeGenerator(this, out); - cg.go(astRoots); - } + new SemanticAnalyzer(this).check(astRoots); + + // Build control flow graph: + for (ClassDecl cd : astRoots) + for (MethodDecl md : cd.methods()) { + new CfgBuilder().build(md); + } + CfgDump.toString(astRoots, ".cfg", cfgdumpbase, false); + + // Optimize + new AstOptimizer().go(astRoots, allTypeSymbols); + CfgDump.toString(astRoots, ".cfgOPT", cfgdumpbase, false); } - /** Dumps the AST to the debug stream */ + public void generateCode(List astRoots, Writer out) { + CfgCodeGenerator cg = new CfgCodeGenerator(this, out); + cg.go(astRoots); + } + + /** + * Dumps the AST to the debug stream + */ private void dumpAst(List astRoots) throws IOException { - if (this.debug == null) return; + if (this.debug == null) + return; this.debug.write(AstDump.toString(astRoots)); } } diff --git a/src/cd/ToDoException.java b/src/cd/ToDoException.java deleted file mode 100644 index 615a148..0000000 --- a/src/cd/ToDoException.java +++ /dev/null @@ -1,14 +0,0 @@ -package cd; - -/** TAs insert this to mark code that students need to write */ -public class ToDoException extends RuntimeException { - private static final long serialVersionUID = 4054810321239901944L; - - public ToDoException() { - } - - public ToDoException(String message) { - super(message); - } - -} diff --git a/src/cd/backend/ExitCode.java b/src/cd/backend/ExitCode.java index 9ec741e..01b2b10 100644 --- a/src/cd/backend/ExitCode.java +++ b/src/cd/backend/ExitCode.java @@ -10,10 +10,10 @@ public enum ExitCode { INFINITE_LOOP(6), DIVISION_BY_ZERO(7), INTERNAL_ERROR(22); - + public final int value; - private ExitCode(int value) { - this.value = value; + private ExitCode(int value) { + this.value = value; } } \ No newline at end of file diff --git a/src/cd/backend/codegen/AssemblyEmitter.java b/src/cd/backend/codegen/AssemblyEmitter.java index 11cbb75..1997c8d 100644 --- a/src/cd/backend/codegen/AssemblyEmitter.java +++ b/src/cd/backend/codegen/AssemblyEmitter.java @@ -15,25 +15,33 @@ public class AssemblyEmitter { this.out = out; } - /** Creates an constant operand. */ + /** + * Creates an constant operand. + */ static String constant(int i) { return "$" + i; } - /** Creates an constant operand with the address of a label. */ + /** + * Creates an constant operand with the address of a label. + */ static String labelAddress(String lbl) { return "$" + lbl; } - /** Creates an operand relative to another operand. */ + /** + * Creates an operand relative to another operand. + */ static String registerOffset(int offset, Register reg) { return String.format("%d(%s)", offset, reg); } - /** Creates an operand addressing an item in an array */ + /** + * Creates an operand addressing an item in an array + */ static String arrayAddress(Register arrReg, Register idxReg) { final int offset = Config.SIZEOF_PTR * 2; // one word each in front for - // vptr and length + // vptr and length final int mul = Config.SIZEOF_PTR; // assume all arrays of 4-byte elem return String.format("%d(%s,%s,%d)", offset, arrReg, idxReg, mul); } @@ -90,10 +98,6 @@ public class AssemblyEmitter { emit(op, constant(src), dest); } - void emit(String op, int src, String dest) { - emit(op, constant(src), dest); - } - void emit(String op, String dest) { emitRaw(op + " " + dest); } @@ -106,10 +110,6 @@ public class AssemblyEmitter { emit(op, constant(dest)); } - void emit(String op, int src, int dest) { - emit(op, constant(src), constant(dest)); - } - void emitMove(Register src, String dest) { emitMove(src.repr, dest); } @@ -127,10 +127,6 @@ public class AssemblyEmitter { emit("movl", src, dest); } - void emitMove(int src, Register dest) { - emitMove(constant(src), dest); - } - void emitLoad(int srcOffset, Register src, Register dest) { emitMove(registerOffset(srcOffset, src), dest.repr); } @@ -143,10 +139,6 @@ public class AssemblyEmitter { emitMove(src, registerOffset(destOffset, dest)); } - void emitStore(int src, int destOffset, Register dest) { - emitStore(constant(src), destOffset, dest); - } - void emitConstantData(String data) { emitRaw(String.format("%s %s", Config.DOT_INT, data)); } diff --git a/src/cd/backend/codegen/AssemblyFailedException.java b/src/cd/backend/codegen/AssemblyFailedException.java index 112fee9..9f6fa7f 100644 --- a/src/cd/backend/codegen/AssemblyFailedException.java +++ b/src/cd/backend/codegen/AssemblyFailedException.java @@ -2,13 +2,14 @@ package cd.backend.codegen; public class AssemblyFailedException extends RuntimeException { private static final long serialVersionUID = -5658502514441032016L; - + public final String assemblerOutput; + public AssemblyFailedException( String assemblerOutput) { super("Executing assembler failed.\n" + "Output:\n" + assemblerOutput); this.assemblerOutput = assemblerOutput; - } + } } diff --git a/src/cd/backend/codegen/AstCodeGenerator.java b/src/cd/backend/codegen/AstCodeGenerator.java index dbcfd7b..3f777a0 100644 --- a/src/cd/backend/codegen/AstCodeGenerator.java +++ b/src/cd/backend/codegen/AstCodeGenerator.java @@ -2,43 +2,42 @@ package cd.backend.codegen; import cd.Config; import cd.Main; +import cd.backend.ExitCode; import cd.backend.codegen.RegisterManager.Register; import cd.ir.Ast; import cd.ir.Ast.ClassDecl; +import cd.ir.Ast.Expr; +import cd.ir.Ast.MethodDecl; import cd.ir.Symbol.*; import java.io.Writer; -import java.util.*; +import java.util.Collections; +import java.util.List; -import static cd.Config.MAIN; +import static cd.Config.*; +import static cd.backend.codegen.AssemblyEmitter.constant; +import static cd.backend.codegen.AssemblyEmitter.registerOffset; import static cd.backend.codegen.RegisterManager.BASE_REG; import static cd.backend.codegen.RegisterManager.STACK_REG; public class AstCodeGenerator { - /** Constant representing the boolean TRUE as integer */ - static final int TRUE = 1; - /** Constant representing the boolean FALSE as integer */ - static final int FALSE = 0; - /** Size of any variable in assembly - * Primitive variables take up 4 bytes (booleans are integers) - * Reference variables are a 4 byte pointer - */ - static final int VAR_SIZE = 4; - RegsNeededVisitor rnv; - - ExprGenerator eg; - StmtGenerator sg; - + protected RegsNeededVisitor rnv; + + protected ExprGenerator eg; + protected StmtGenerator sg; + protected final Main main; - - final AssemblyEmitter emit; - final RegisterManager rm = new RegisterManager(); + + protected final AssemblyEmitter emit; + protected final RegisterManager rm = new RegisterManager(); + + protected ExprGeneratorRef egRef; + protected StmtGeneratorRef sgRef; AstCodeGenerator(Main main, Writer out) { initMethodData(); - main.allTypeSymbols = new ArrayList<>(); - + this.emit = new AssemblyEmitter(out); this.main = main; this.rnv = new RegsNeededVisitor(); @@ -52,14 +51,15 @@ public class AstCodeGenerator { } public static AstCodeGenerator createCodeGenerator(Main main, Writer out) { - return new AstCodeGenerator(main, out); + return new AstCodeGeneratorRef(main, out); } + /** * Main method. Causes us to emit x86 assembly corresponding to {@code ast} * into {@code file}. Throws a {@link RuntimeException} should any I/O error * occur. - * + * *

* The generated file will be divided into two sections: *

    @@ -70,103 +70,693 @@ public class AstCodeGenerator { *
*/ public void go(List astRoots) { - // Find main type and assign to main.mainType - // Add array types to the types list to lookup later - for (ClassDecl decl : astRoots) { - if (decl.name.equals("Main")) { - main.mainType = decl.sym; - } - main.allTypeSymbols.add(new ArrayTypeSymbol(decl.sym)); - } - - main.allTypeSymbols.add(new ArrayTypeSymbol(PrimitiveTypeSymbol.intType)); - main.allTypeSymbols.add(new ArrayTypeSymbol(PrimitiveTypeSymbol.booleanType)); - main.allTypeSymbols.add(new ArrayTypeSymbol(ClassSymbol.objectType)); - - emitPrefix(); for (ClassDecl ast : astRoots) { sg.gen(ast); } } - private void emitPrefix() { - // Emit some useful string constants (copied from old HW1 method declaration) + + protected void initMethodData() { + rm.initRegisters(); + } + + + protected void emitMethodSuffix(boolean returnNull) { + if (returnNull) + emit.emit("movl", "$0", Register.EAX); + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); + } +} + +class AstCodeGeneratorRef extends AstCodeGenerator { + /** + * The address of the this ptr relative to the BP. Note that the this ptr is + * always the first argument. Default offset value is 8 but this can change + * depending on the number of parameters pushed on the stack. + */ + protected int THIS_OFFSET = 8; + + /** + * Name of the internal Javali$CheckCast() helper function we generate. + */ + static final String CHECK_CAST = "Javali$CheckCast"; + + /** + * Name of the internal Javali$CheckNull() helper function we generate. + */ + static final String CHECK_NULL = "Javali$CheckNull"; + + /** + * Name of the internal Javali$CheckNonZero() helper function we generate. + */ + static final String CHECK_NON_ZERO = "Javali$CheckNonZero"; + + /** + * Name of the internal Javali$CheckArraySize() helper function we generate. + */ + static final String CHECK_ARRAY_SIZE = "Javali$CheckArraySize"; + + /** + * Name of the internal Javali$CheckArrayBounds() helper function we + * generate. + */ + static final String CHECK_ARRAY_BOUNDS = "Javali$CheckArrayBounds"; + + /** + * Name of the internal Javali$Alloc() helper function we generate. + */ + static final String ALLOC = "Javali$Alloc"; + + /** + * Name of the internal Javali$PrintNewLine() helper function we generate. + */ + static final String PRINT_NEW_LINE = "Javali$PrintNewLine"; + + /** + * Name of the internal Javali$PrintInteger() helper function we generate. + */ + static final String PRINT_INTEGER = "Javali$PrintInteger"; + + /** + * Name of the internal Javali$ReadInteger() helper function we generate. + */ + static final String READ_INTEGER = "Javali$ReadInteger"; + + public AstCodeGeneratorRef(Main main, Writer out) { + super(main, out); + + this.egRef = new ExprGeneratorRef(this); + this.eg = this.egRef; + this.sgRef = new StmtGeneratorRef(this); + this.sg = this.sgRef; + } + + + protected void emitPrefix(List astRoots) { + // compute method and field offsets + for (ClassDecl ast : astRoots) { + computeFieldOffsets(ast.sym); + computeVtableOffsets(ast.sym); + } + + // emit vtables + for (TypeSymbol ts : main.allTypeSymbols) + emitVtable(ts); + + // Emit some useful string constants and static data: emit.emitRaw(Config.DATA_STR_SECTION); emit.emitLabel("STR_NL"); emit.emitRaw(Config.DOT_STRING + " \"\\n\""); emit.emitLabel("STR_D"); emit.emitRaw(Config.DOT_STRING + " \"%d\""); - - // Define Object, Object[], int, int[], boolean and boolean[] - List elementTypes = new ArrayList<>(); - elementTypes.add(ClassSymbol.objectType); - elementTypes.add(PrimitiveTypeSymbol.intType); - elementTypes.add(PrimitiveTypeSymbol.booleanType); + emit.emitLabel("STR_F"); + emit.emitRaw(Config.DOT_STRING + " \"%.5f\""); + emit.emitLabel("SCANF_STR_F"); + emit.emitRaw(Config.DOT_STRING + " \"%f\""); emit.emitRaw(Config.DATA_INT_SECTION); - for (TypeSymbol type : elementTypes) { - // type vtable - emit.emitLabel(Label.type(type)); - emit.emitConstantData("0"); // Supertype (null) - // array vtable - emit.emitLabel(Label.type(new ArrayTypeSymbol(type))); - emit.emitConstantData(Label.type(ClassSymbol.objectType)); // Supertype - emit.emitConstantData(Label.type(type)); // Element type + + emit.emitRaw(Config.TEXT_SECTION); + + // Generate a helper method for checking casts: + // It takes first a vtable and second an object ptr. + { + Register obj = RegisterManager.CALLER_SAVE[0]; + Register cls = RegisterManager.CALLER_SAVE[1]; + String looplbl = emit.uniqueLabel(); + String donelbl = emit.uniqueLabel(); + String faillbl = emit.uniqueLabel(); + emit.emitCommentSection(CHECK_CAST + " function"); + emit.emitLabel(CHECK_CAST); + + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(8), STACK_REG); + + emit.emit("and", constant(-16), STACK_REG); + emit.emit("sub", constant(16), STACK_REG); + emit.emitLoad(SIZEOF_PTR * 2, BASE_REG, cls); + emit.emitLoad(SIZEOF_PTR * 3, BASE_REG, obj); + emit.emit("cmpl", constant(0), obj); + emit.emit("je", donelbl); // allow null objects to pass + emit.emitLoad(0, obj, obj); // load vtbl of object + emit.emitLabel(looplbl); + emit.emit("cmpl", obj, cls); + emit.emit("je", donelbl); + emit.emit("cmpl", constant(0), obj); + emit.emit("je", faillbl); + emit.emitLoad(0, obj, obj); // load parent vtable + emit.emit("jmp", looplbl); + emit.emitLabel(faillbl); + emit.emitStore(constant(ExitCode.INVALID_DOWNCAST.value), 0, STACK_REG); + emit.emit("call", Config.EXIT); + emit.emitLabel(donelbl); + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); } - // Emit the new Main().main() code to start the program: + // Generate a helper method for checking for null ptrs: + { + String oknulllbl = emit.uniqueLabel(); + emit.emitCommentSection(CHECK_NULL + " function"); + emit.emitLabel(CHECK_NULL); - // 1. Enter TEXT and start the program - emit.emitRaw(Config.TEXT_SECTION); - emit.emit(".globl", MAIN); - emit.emitLabel(MAIN); - // 1.1. Prepare first frame - emit.emit("push", BASE_REG); - emit.emitMove(STACK_REG, BASE_REG); + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(8), STACK_REG); - // 2. Create main variable + emit.emit("and", constant(-16), STACK_REG); + emit.emit("sub", constant(16), STACK_REG); + emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG)); + emit.emit("jne", oknulllbl); + emit.emitStore(constant(ExitCode.NULL_POINTER.value), 0, STACK_REG); + emit.emit("call", Config.EXIT); + emit.emitLabel(oknulllbl); + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); + } + + // Generate a helper method for checking that we don't divide by zero: + { + String oknzlbl = emit.uniqueLabel(); + emit.emitCommentSection(CHECK_NON_ZERO + " function"); + emit.emitLabel(CHECK_NON_ZERO); + + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(8), STACK_REG); + + emit.emit("and", constant(-16), STACK_REG); + emit.emit("sub", constant(16), STACK_REG); + emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG)); + emit.emit("jne", oknzlbl); + emit.emitStore(constant(ExitCode.DIVISION_BY_ZERO.value), 0, STACK_REG); + emit.emit("call", Config.EXIT); + emit.emitLabel(oknzlbl); + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); + } + + // Generate a helper method for checking array size: + { + String okunqlbl = emit.uniqueLabel(); + emit.emitCommentSection(CHECK_ARRAY_SIZE + " function"); + emit.emitLabel(CHECK_ARRAY_SIZE); + + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(8), STACK_REG); + + emit.emit("and", constant(-16), STACK_REG); + emit.emit("sub", constant(16), STACK_REG); + emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG)); + emit.emit("jge", okunqlbl); + emit.emitStore(constant(ExitCode.INVALID_ARRAY_SIZE.value), 0, STACK_REG); + emit.emit("call", Config.EXIT); + emit.emitLabel(okunqlbl); + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); + } + + // Generate a helper method for checking array bounds: + { + Register arr = RegisterManager.CALLER_SAVE[0]; + Register idx = RegisterManager.CALLER_SAVE[1]; + String faillbl = emit.uniqueLabel(); + emit.emitCommentSection(CHECK_ARRAY_BOUNDS + " function"); + emit.emitLabel(CHECK_ARRAY_BOUNDS); + + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(8), STACK_REG); + + emit.emit("and", constant(-16), STACK_REG); + emit.emit("sub", constant(16), STACK_REG); + emit.emitLoad(SIZEOF_PTR * 3, BASE_REG, idx); + emit.emitLoad(SIZEOF_PTR * 2, BASE_REG, arr); + emit.emit("cmpl", constant(0), idx); // idx < 0 + emit.emit("jl", faillbl); + emit.emit("cmpl", registerOffset(Config.SIZEOF_PTR, arr), idx); // idx >= len + emit.emit("jge", faillbl); + // done + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); + // fail + emit.emitLabel(faillbl); + emit.emitStore(constant(ExitCode.INVALID_ARRAY_BOUNDS.value), 0, STACK_REG); + emit.emit("call", Config.EXIT); + + } + + // Generate a helper method for allocating objects/arrays + { + Register size = RegisterManager.CALLER_SAVE[0]; + emit.emitCommentSection(ALLOC + " function"); + emit.emitLabel(ALLOC); + + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(8), STACK_REG); + + emit.emit("and", constant(-16), STACK_REG); + emit.emit("sub", constant(16), STACK_REG); + emit.emitLoad(8, BASE_REG, size); + emit.emitStore(size, 0, STACK_REG); + emit.emitStore(constant(1), 4, STACK_REG); + emit.emit("call", Config.CALLOC); + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); + } + + // Generate a helper method for printing a new line + { + emit.emitCommentSection(PRINT_NEW_LINE + " function"); + emit.emitLabel(PRINT_NEW_LINE); + + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(8), STACK_REG); + + emit.emit("and", constant(-16), STACK_REG); + emit.emit("sub", constant(16), STACK_REG); + emit.emitStore("$STR_NL", 0, STACK_REG); + emit.emit("call", Config.PRINTF); + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); + } + + // Generate a helper method for printing an integer + { + Register temp = RegisterManager.CALLER_SAVE[0]; + emit.emitCommentSection(PRINT_INTEGER + " function"); + emit.emitLabel(PRINT_INTEGER); + + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(8), STACK_REG); + + emit.emit("and", constant(-16), STACK_REG); + emit.emit("sub", constant(16), STACK_REG); + emit.emitLoad(8, BASE_REG, temp); + emit.emitStore(temp, 4, STACK_REG); + emit.emitStore("$STR_D", 0, STACK_REG); + emit.emit("call", Config.PRINTF); + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); + } + + // Generate a helper method for reading an integer + { + Register number = RegisterManager.CALLER_SAVE[0]; + emit.emitCommentSection(READ_INTEGER + " function"); + emit.emitLabel(READ_INTEGER); + + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(8), STACK_REG); + + emit.emit("and", constant(-16), STACK_REG); + emit.emit("sub", constant(16), STACK_REG); + emit.emit("leal", registerOffset(8, STACK_REG), number); + emit.emitStore(number, 4, STACK_REG); + emit.emitStore("$STR_D", 0, STACK_REG); + emit.emit("call", SCANF); + emit.emitLoad(8, STACK_REG, Register.EAX); + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); + } + + // Generate AST for main() method: + // new Main().main() Ast.NewObject newMain = new Ast.NewObject("Main"); newMain.type = main.mainType; - Register mainLocation = eg.visit(newMain, null); - // 3. Call main() - emit.emit("push", mainLocation); - for (ClassSymbol sym = main.mainType; sym != ClassSymbol.objectType; sym = sym.superClass) - if (sym.methods.getOrDefault("main", null) != null) { - emit.emit("call", Label.method(sym, sym.methods.get("main"))); - break; - } - emitMethodSuffix(true); + Ast.MethodCallExpr mce = new Ast.MethodCallExpr(newMain, "main", Collections.emptyList()); + Ast.MethodCall callMain = new Ast.MethodCall(mce); + mce.sym = main.mainType.getMethod("main"); + + // Emit the main() method: + // new Main().main(); + emit.emitCommentSection("main() function"); + emit.emitRaw(".globl " + MAIN); + emit.emitLabel(MAIN); + + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(8), STACK_REG); + + emit.emit("and", -16, STACK_REG); + sg.gen(callMain); + emit.emit("movl", constant(ExitCode.OK.value), Register.EAX); // normal termination: + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); + emit.emitRaw("ret"); + } - void initMethodData() { - rm.initRegisters(); + @Override + public void go(List astRoots) { + emitPrefix(astRoots); + super.go(astRoots); + } + + /** + * Computes the vtable offset for each method defined in the class + * {@code sym}. + */ + + protected int computeVtableOffsets(ClassSymbol sym) { + + if (sym == null) + return 0; + + if (sym.totalMethods != -1) + return sym.totalMethods; + + int index = computeVtableOffsets(sym.superClass); + for (MethodSymbol ms : sym.methods.values()) { + assert ms.vtableIndex == -1; + if (ms.overrides != null) + ms.vtableIndex = ms.overrides.vtableIndex; + else + ms.vtableIndex = index++; + } + sym.totalMethods = index; + return index; + } + + /** + * Computes the offset for each field. + */ + + protected int computeFieldOffsets(ClassSymbol sym) { + if (sym == null) + return 0; + + if (sym.totalFields != -1) + return sym.totalFields; + + int index = computeFieldOffsets(sym.superClass); + for (VariableSymbol fs : sym.fields.values()) { + assert fs.offset == -1; + // compute offset in bytes; note that 0 is the vtable + fs.offset = (index * SIZEOF_PTR) + SIZEOF_PTR; + index++; + } + sym.totalFields = index; + sym.sizeof = (sym.totalFields + 1) * Config.SIZEOF_PTR; + return index; + } + + private void collectVtable(MethodSymbol[] vtable, ClassSymbol sym) { + if (sym.superClass != null) + collectVtable(vtable, sym.superClass); + for (MethodSymbol ms : sym.methods.values()) + vtable[ms.vtableIndex] = ms; + } + + protected void emitVtable(TypeSymbol ts) { + if (ts instanceof ClassSymbol) { + ClassSymbol cs = (ClassSymbol) ts; + + // Collect the vtable: + MethodSymbol[] vtable = new MethodSymbol[cs.totalMethods]; + collectVtable(vtable, cs); + + // Emit vtable for this class: + emit.emitLabel(vtable(cs)); + if (cs.superClass != null) + emit.emitConstantData(vtable(cs.superClass)); + else + emit.emitConstantData("0"); + for (int i = 0; i < cs.totalMethods; i++) + emit.emitConstantData(methodLabel(vtable[i])); + } else if (ts instanceof ArrayTypeSymbol) { + ArrayTypeSymbol as = (ArrayTypeSymbol) ts; + emit.emitLabel(vtable(as)); + emit.emitConstantData(vtable(ClassSymbol.objectType)); + } + } + + protected String vtable(TypeSymbol ts) { + if (ts instanceof ClassSymbol) { + return "vtable_" + ((ClassSymbol) ts).name; + } else if (ts instanceof ArrayTypeSymbol) { + return "vtablearr_" + ((ArrayTypeSymbol) ts).elementType.name; + } else { + throw new RuntimeException("No vtable for " + ts.name); + } } - void emitMethodSuffix(boolean returnNull) { + @Override + protected void initMethodData() { + THIS_OFFSET = 8; + bytes = 0; + super.initMethodData(); + } + + protected int padding(int numberOfParameters) { + int padding = (bytes + numberOfParameters * Config.SIZEOF_PTR + 15) & 0xFFFFFFF0; + return padding - bytes - numberOfParameters * Config.SIZEOF_PTR; + } + + protected void push(int padding) { + if (padding > 0) { + emit.emit("sub", padding, STACK_REG); + bytes += padding; + } + } + + protected void pop(int padding) { + if (padding > 0) { + emit.emit("add", padding, STACK_REG); + bytes -= padding; + } + assert bytes >= 0; + } + + protected void push(String reg) { + emit.emit("push", reg); + bytes += Config.SIZEOF_PTR; + } + + protected void pop(String reg) { + emit.emit("pop", reg); + bytes -= Config.SIZEOF_PTR; + assert bytes >= 0; + } + + protected void restoreCalleeSaveRegs() { + for (int reg = RegisterManager.CALLEE_SAVE.length - 1; reg >= 0; reg--) { + emit.emit("pop", RegisterManager.CALLEE_SAVE[reg]); + } + } + + protected void storeCalleeSaveRegs() { + bytes = 0; + for (int reg = 0; reg < RegisterManager.CALLEE_SAVE.length; reg++) { + emit.emit("push", RegisterManager.CALLEE_SAVE[reg]); + bytes += Config.SIZEOF_PTR; + } + } + + protected void restoreCallerSaveRegs(Register res) { + for (int reg = RegisterManager.CALLER_SAVE.length - 1; reg >= 0; reg--) { + if (!rm.isInUse(RegisterManager.CALLER_SAVE[reg])) + continue; // not in use + if (RegisterManager.CALLER_SAVE[reg].equals(res)) + continue; // contains our result + pop(RegisterManager.CALLER_SAVE[reg].repr); + } + } + + protected void storeCallerSaveRegs(Register res) { + for (int reg = 0; reg < RegisterManager.CALLER_SAVE.length; reg++) { + if (!rm.isInUse(RegisterManager.CALLER_SAVE[reg])) + continue; // not in use + if (RegisterManager.CALLER_SAVE[reg].equals(res)) + continue; // will contain our result + push(RegisterManager.CALLER_SAVE[reg].repr); + } + } + + protected int emitCallPrefix(Register res, int numberOfParameters) { + storeCallerSaveRegs(res); + int padding = padding(numberOfParameters); + push(padding); + return padding; + } + + protected void emitCallSuffix(Register res, int numberOfParameters, + int padding) { + pop(numberOfParameters * Config.SIZEOF_PTR + padding); + if (res != null) { + emit.emitMove(Register.EAX, res); + } + restoreCallerSaveRegs(res); + } + + + /** + * Generates code which evaluates {@code ast} and branches to {@code lbl} if + * the value generated for {@code ast} is false. + */ + + protected void genJumpIfFalse(Expr ast, String lbl) { + // A better way to implement this would be with a separate + // visitor. + Register reg = eg.gen(ast); + emit.emit("cmpl", "$0", reg); + emit.emit("je", lbl); + rm.releaseRegister(reg); + } + + + /** + * Used to store the temporaries. We grow our stack dynamically, we allocate + * "temporary" values on this stack during method execution. Values can be + * stored and retrieved using {@link #push(String)} and {@link #pop(String)} + * , which use the program stack. + */ + + protected int bytes = 0; + + protected String methodLabel(MethodSymbol msym) { + return "meth_" + msym.owner.name + "_" + msym.name; + } + + protected void emitMethodPrefix(MethodDecl ast) { + + // Emit the label for the method: + emit.emitRaw(Config.TEXT_SECTION); + emit.emitCommentSection(String.format("Method %s.%s", ast.sym.owner.name, + ast.name)); + emit.emitRaw(".globl " + methodLabel(ast.sym)); + emit.emitLabel(methodLabel(ast.sym)); + + // Compute the size and layout of the stack frame. Our + // frame looks like (the numbers are relative to our ebp): + // + // (caller's locals) + // (padding) + // arg 0 (this ptr) + // ... + // 12 arg N - 1 + // 8 arg N + // 4 linkage ptr (return address) + // 0 saved ebp + // -4 locals + // (callee's arguments + temporaries) + // + // We allocate on the stack during the course of + // a function call using push(...) and pop(...) instructions. + // + // Stack slots fall into several + // categories: + // - "Linkage": overhead for function calls. + // This includes the return address and saved ebp. + // - locals: these store the value of user-declared local + // variables. + // - temporaries: these are stack slots used to store + // values during expression evaluation when we run out + // of registers, saving caller-saved registers, and + // other miscellaneous purposes. + // - padding: only there to ensure the stack size is a multiple + // of 16. + // - arguments: values we will pass to functions being + // invoked. + // + // We calculate all address relative to the base pointer. + + // Initialize method-specific data + initMethodData(); + + // Assign parameter offsets: + // As shown above, these start from 8. + // Being able to evaluate parameters like in Java + // with left-to-right evaluation order they result + // on the stack in reversed order. + // The "this" parameter is the first pushed on the stack + // thus receiving higher offset. + int paramOffset = Config.SIZEOF_PTR * 2; + for (int i = ast.sym.parameters.size() - 1; i >= 0; i--) { + final VariableSymbol param = ast.sym.parameters.get(i); + assert param.offset == -1; + param.offset = paramOffset; + paramOffset += Config.SIZEOF_PTR; + } + THIS_OFFSET = paramOffset; + paramOffset += Config.SIZEOF_PTR; + + // First few slots are reserved for caller save regs: + int localSlot = RegisterManager.CALLER_SAVE.length * RegisterManager.SIZEOF_REG; + + // Assign local variable offsets: + emit.emitComment(String.format("%-10s Offset", "Variable")); + for (VariableSymbol local : ast.sym.locals.values()) { + assert local.offset == -1; + local.offset = -localSlot; + localSlot += Config.SIZEOF_PTR; + emit.emitComment(String.format("%-10s %d", local, local.offset)); + } + + // Round up stack size to make it a multiple of 16. + // The actual amount passed to the enter instruction is 8 + // less, however, because it wants to know the amount + // in addition to the linkage ptr and saved ebp. + int implicit = Config.SIZEOF_PTR * 2; + int stackSize = (implicit + localSlot + 15) & 0xFFFFFFF0; + stackSize -= implicit; + + emit.emitComment(String.format("implicit=%d localSlot=%d sum=%d", implicit, + localSlot, implicit + localSlot)); + + + // emit.emitRaw(String.format("enter $%d, $0", stackSize)); + emit.emit("push", BASE_REG); + emit.emitMove(STACK_REG, BASE_REG); + emit.emit("sub", constant(stackSize), STACK_REG); + + emit.emit("and", -16, STACK_REG); + + storeCalleeSaveRegs(); + + // zero-initialize locals + for (VariableSymbol local : ast.sym.locals.values()) { + emit.emitMove(constant(0), registerOffset(local.offset, BASE_REG)); + } + } + + @Override + protected void emitMethodSuffix(boolean returnNull) { if (returnNull) - emit.emit("movl", 0, Register.EAX); - emit.emitRaw("leave"); + emit.emit("movl", "$0", Register.EAX); + restoreCalleeSaveRegs(); + emit.emitMove(BASE_REG, STACK_REG); + emit.emit("pop", BASE_REG); emit.emitRaw("ret"); } - - static class Label { - static String type(TypeSymbol symbol) { - if (symbol instanceof ClassSymbol) - return String.format("type_%s", symbol); - else if (symbol instanceof ArrayTypeSymbol) - return String.format("array_%s", ((ArrayTypeSymbol) symbol).elementType); - else if (symbol instanceof PrimitiveTypeSymbol) - return String.format("primive_%s", symbol); - throw new RuntimeException("Unimplemented type symbol"); - } - - static String method(ClassSymbol classSymbol, MethodSymbol methodSymbol) { - return String.format("method_%s_%s", classSymbol.name, methodSymbol.name); - } - - static String returnMethod(ClassSymbol classSymbol, MethodSymbol methodSymbol) { - return String.format("return_%s", method(classSymbol, methodSymbol)); - } - } +} + +class AstCodeGeneratorNop90 extends AstCodeGeneratorRef { + public AstCodeGeneratorNop90(Main main, Writer out, CfgCodeGenerator cfgCg) { + super(main, out); + this.egRef = new ExprGeneratorNop90(this, cfgCg); + this.eg = this.egRef; + this.sgRef = new StmtGeneratorNop90(this, cfgCg); + this.sg = this.sgRef; + } + + } diff --git a/src/cd/backend/codegen/CfgCodeGenerator.java b/src/cd/backend/codegen/CfgCodeGenerator.java new file mode 100644 index 0000000..c39079a --- /dev/null +++ b/src/cd/backend/codegen/CfgCodeGenerator.java @@ -0,0 +1,130 @@ +package cd.backend.codegen; + +import cd.Main; +import cd.ir.Ast.ClassDecl; +import cd.ir.Ast.MethodDecl; +import cd.ir.Ast.Stmt; +import cd.ir.Ast; +import cd.ir.AstVisitor; +import cd.ir.BasicBlock; +import cd.ir.ControlFlowGraph; +import cd.ir.Symbol.PrimitiveTypeSymbol; +import cd.ir.Symbol.TypeSymbol; +import cd.ir.Symbol.VariableSymbol; +import cd.transform.analysis.ArraySizeAnalysis; +import cd.transform.analysis.AssignmentAnalysis; +import cd.transform.analysis.DynamicTypeAnalysis; +import cd.transform.analysis.MaybeC; +import cd.transform.analysis.NullAnalysis; + +import java.io.Writer; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CfgCodeGenerator { + + public final Main main; + private final AstCodeGeneratorRef cg; + public final CompileTimeChecks check; + + protected Map> arraySizeState; + protected Map> nullState; + protected Map> dynamicTypeState; + //protected Map>> unusedAssignmentsState; + + public CfgCodeGenerator(Main main, Writer out) { + this.main = main; + cg = new AstCodeGeneratorNop90(main, out, this); + check = new CompileTimeChecks(cg, this); + } + + public void go(List astRoots) { + cg.emitPrefix(astRoots); + for (ClassDecl cdecl : astRoots) + new CfgStmtVisitor().visit(cdecl, null); + } + + private class CfgStmtVisitor extends AstVisitor { + + @Override + public Void classDecl(ClassDecl ast, Void arg) { + cg.emit.emitCommentSection("Class " + ast.name); + cg.emit.increaseIndent(""); + super.classDecl(ast, arg); + cg.emit.decreaseIndent(); + return null; + } + + @Override + public Void methodDecl(MethodDecl ast, Void arg) { + cg.emitMethodPrefix(ast); + + ControlFlowGraph cfg = ast.cfg; + assert cfg != null; + + Map labels = new HashMap(); + for (BasicBlock blk : cfg.allBlocks) + labels.put(blk, cg.emit.uniqueLabel()); + String exitLabel = cg.emit.uniqueLabel(); + + cg.emit.emit("jmp", labels.get(cfg.start)); + + // Run analysis here to have the relevant results to this cfg object + ArraySizeAnalysis arraySizeAnalysis = new ArraySizeAnalysis(cfg); + NullAnalysis nullAnalysis = new NullAnalysis(cfg); + DynamicTypeAnalysis dynamicTypeAnalysis = new DynamicTypeAnalysis(cfg); + //AssignmentAnalysis assignmentAnalysis = new AssignmentAnalysis(cfg); + + for (BasicBlock blk : cfg.allBlocks) { + arraySizeState = arraySizeAnalysis.inStateOf(blk); + nullState = nullAnalysis.inStateOf(blk); + dynamicTypeState = dynamicTypeAnalysis.inStateOf(blk); + //unusedAssignmentsState = assignmentAnalysis.inStateOf(blk); + assert nullState != null; + assert arraySizeState != null; + assert dynamicTypeState != null; + //assert unusedAssignmentsState != null; + + cg.emit.emitCommentSection("Basic block " + blk.index); + cg.emit.emitLabel(labels.get(blk)); + + for (Stmt stmt : blk.stmts) { + cg.sg.gen(stmt); + //assignmentAnalysis.transferStmt(stmt, unusedAssignmentsState); + arraySizeAnalysis.transferStmt(stmt, arraySizeState); + nullAnalysis.transferStmt(stmt, nullState); + dynamicTypeAnalysis.transferStmt(stmt, dynamicTypeState); + } + + if (blk == cfg.end) { + cg.emit.emitComment(String.format("Return")); + assert blk.successors.size() == 0; + cg.emit.emit("jmp", exitLabel); + } else if (blk.condition != null) { + assert blk.successors.size() == 2; + cg.emit.emitComment(String.format( + "Exit to block %d if true, block %d if false", + blk.trueSuccessor().index, blk.falseSuccessor().index)); + cg.genJumpIfFalse(blk.condition, labels.get(blk.falseSuccessor())); + cg.emit.emit("jmp", labels.get(blk.trueSuccessor())); + } else { + cg.emit.emitComment(String.format( + "Exit to block %d", blk.successors.get(0).index)); + assert blk.successors.size() == 1; + cg.emit.emit("jmp", labels.get(blk.successors.get(0))); + } + } + + cg.emit.emitLabel(exitLabel); + if (ast.sym.returnType.equals(PrimitiveTypeSymbol.voidType)) + cg.emitMethodSuffix(true); + else + cg.emitMethodSuffix(true); + + return null; + } + + } +} + diff --git a/src/cd/backend/codegen/CompileTimeChecks.java b/src/cd/backend/codegen/CompileTimeChecks.java new file mode 100755 index 0000000..d68940d --- /dev/null +++ b/src/cd/backend/codegen/CompileTimeChecks.java @@ -0,0 +1,106 @@ +package cd.backend.codegen; + +import cd.Config; +import cd.backend.ExitCode; +import cd.ir.Ast; +import cd.ir.Ast.*; +import cd.ir.CompileTimeEvaluator; +import cd.ir.Symbol.VariableSymbol; +import cd.transform.analysis.MaybeC; + +import java.util.Optional; + +import static cd.backend.codegen.AssemblyEmitter.constant; +import static cd.backend.codegen.RegisterManager.STACK_REG; + +/** + * Class with simple static checks mimicking the runtime system with the functions to check + * possible errors at runtime. The purpose is to skip unnecessary calls to those functions + */ +public class CompileTimeChecks { + protected final CfgCodeGenerator cfgCg; + protected final AstCodeGeneratorRef cgRef; + protected CompileTimeEvaluator cte; + + + public CompileTimeChecks(AstCodeGeneratorRef cg, CfgCodeGenerator cfgCodeGenerator) { + this.cfgCg = cfgCodeGenerator; + this.cgRef = cg; + cte = new CompileTimeEvaluator(); + } + + /** + * Convenience method that extracts the VariableSymbol from a Var or Field node. + *
If the node is not one of those, the node must be a Index + */ + private VariableSymbol symbolOf(Expr expr) { + if (expr instanceof Var) + return ((Var) expr).sym; + if (expr instanceof Field) + return ((Field) expr).sym; + // assert expr instanceof Index // only for local checks + return null; + } + + + /** Returns whether a NewArray expression needs to check dynamically for the + * validity of the index (>=0). In case that it is known but invalid, a call + * to exit is done with the appropriate error code. + */ + public boolean checkArraySize(Ast.NewArray expr) { + Optional arraySize = cte.calc(expr.arg()); + if (!arraySize.isPresent()) { + return true; + } + int arraySizeValue = arraySize.get(); + if (arraySizeValue < 0) { + cgRef.emit.emitStore(constant(ExitCode.INVALID_ARRAY_SIZE.value), 0, STACK_REG); + cgRef.emit.emit("call", Config.EXIT); + return false; + } + return false; + } + + + /** Returns whether a run-time check for the bounds of an array index is necessary. + *
A check is only necessary if the index expression cannot be evaluated in + * compile time or the size of the array is unknown. + *
If the value is known and it is invalid, an unconditional call to exit is + * performed with the appropriate exit code + */ + public boolean checkArrayBound(Index expr) { + Optional index = cte.calc(expr.right()); + if (!index.isPresent()) { + return true; + } + VariableSymbol symbol = symbolOf(expr.left()); + if (!cfgCg.arraySizeState.containsKey(symbol)) { + return true; + } + int indexValue = index.get(); + MaybeC bound = cfgCg.arraySizeState.get(symbol); + if (bound.isNotConstant()) { + return true; + } + int boundValue = bound.getValue(); + if (indexValue < 0 || indexValue > (boundValue - 1)) { + cgRef.emit.emitStore(constant(ExitCode.INVALID_ARRAY_BOUNDS.value), 0, STACK_REG); + cgRef.emit.emit("call", Config.EXIT); + } + return false; + } + + /** + * Returns true only when it is impossible for the expression (Field or Var) to be null. + *
Currently doesn't check indexes. + */ + public boolean isNotNull(Expr expr) { + if (expr instanceof ThisRef) { + return false; + } else { + VariableSymbol symbol = symbolOf(expr); + MaybeC isNull = cfgCg.nullState.get(symbol); + return isNull != null && isNull.isConstant() && !isNull.getValue(); + } + } +} diff --git a/src/cd/backend/codegen/ExprGenerator.java b/src/cd/backend/codegen/ExprGenerator.java index 92d0887..a1043d1 100644 --- a/src/cd/backend/codegen/ExprGenerator.java +++ b/src/cd/backend/codegen/ExprGenerator.java @@ -1,30 +1,35 @@ package cd.backend.codegen; +import cd.Config; import cd.backend.ExitCode; -import cd.backend.codegen.AstCodeGenerator.*; import cd.backend.codegen.RegisterManager.Register; import cd.ir.Ast.*; +import cd.ir.Ast.BinaryOp.BOp; +import cd.ir.Ast.UnaryOp.UOp; +import cd.ir.CompileTimeEvaluator; import cd.ir.ExprVisitor; -import cd.ir.Symbol.ClassSymbol; -import cd.ir.Symbol.MethodSymbol; -import cd.ir.Symbol.PrimitiveTypeSymbol; -import cd.ir.Symbol.VariableSymbol; +import cd.ir.Symbol.*; +import cd.transform.analysis.MaybeC; +import cd.util.Pair; import cd.util.debug.AstOneLine; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; -import static cd.Config.SCANF; -import static cd.backend.codegen.AstCodeGenerator.*; +import static cd.Config.FALSE; +import static cd.Config.TRUE; +import static cd.backend.codegen.AssemblyEmitter.constant; +import static cd.backend.codegen.AssemblyEmitter.labelAddress; import static cd.backend.codegen.RegisterManager.BASE_REG; -import static cd.backend.codegen.RegisterManager.CALLER_SAVE; -import static cd.backend.codegen.RegisterManager.STACK_REG; /** * Generates code to evaluate expressions. After emitting the code, returns a * String which indicates the register where the result can be found. */ -class ExprGenerator extends ExprVisitor { - private final AstCodeGenerator cg; +class ExprGenerator extends ExprVisitor { + protected final AstCodeGenerator cg; ExprGenerator(AstCodeGenerator astCodeGenerator) { cg = astCodeGenerator; @@ -35,27 +40,10 @@ class ExprGenerator extends ExprVisitor { } @Override - public Register visit(Expr ast, Location arg) { + public Register visit(Expr ast, Void arg) { try { cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast)); - if (cg.rnv.calc(ast) > cg.rm.availableRegisters()) { - Deque pushed = new ArrayDeque<>(); - for (Register r : RegisterManager.GPR) { - if (cg.rm.isInUse(r)) { - cg.emit.emit("push", r); - pushed.push(r); - cg.rm.releaseRegister(r); - } - } - Register result = super.visit(ast, arg); - for (Register r : pushed) - cg.rm.useRegister(r); - Register finalResult = cg.rm.getRegister(); - cg.emit.emitMove(result, finalResult); - for (Register r = pushed.pop(); !pushed.isEmpty(); r = pushed.pop()) - cg.emit.emit("pop", r); - return finalResult; - } else return super.visit(ast, arg); + return super.visit(ast, null); } finally { cg.emit.decreaseIndent(); } @@ -63,551 +51,906 @@ class ExprGenerator extends ExprVisitor { } @Override - public Register binaryOp(BinaryOp ast, Location arg) { - // Simplistic HW1 implementation that does - // not care if it runs out of registers - - int leftRN = cg.rnv.calc(ast.left()); - int rightRN = cg.rnv.calc(ast.right()); - - Register leftReg, rightReg; - if (leftRN > rightRN) { - leftReg = visit(ast.left(), arg); - rightReg = visit(ast.right(), arg); - } else { - rightReg = visit(ast.right(), arg); - leftReg = visit(ast.left(), arg); + public Register intConst(IntConst ast, Void arg) { + { + Register reg = cg.rm.getRegister(); + cg.emit.emit("movl", ast.value, reg); + return reg; } + } - cg.debug("Binary Op: %s (%s,%s)", ast, leftReg, rightReg); - - switch (ast.operator) { - case B_TIMES: - cg.emit.emit("imul", rightReg, leftReg); - break; - case B_PLUS: - cg.emit.emit("add", rightReg, leftReg); - break; - case B_MINUS: - cg.emit.emit("sub", rightReg, leftReg); - break; - case B_DIV: - case B_MOD: - // Check division by 0 - String beginDivision = cg.emit.uniqueLabel(); - cg.emit.emit("cmp", 0, rightReg); - cg.emit.emit("jne", beginDivision); - Interrupts.exit(cg, ExitCode.DIVISION_BY_ZERO); - cg.emit.emitLabel(beginDivision); - - // Save EAX, EBX, and EDX to the stack if they are not used - // in this subtree (but are used elsewhere). We will be - // changing them. - List dontBother = Arrays.asList(rightReg, leftReg); - Register[] affected = { Register.EAX, Register.EBX, Register.EDX }; - - for (Register s : affected) - if (!dontBother.contains(s) && cg.rm.isInUse(s)) - cg.emit.emit("pushl", s); - - // Move the LHS (numerator) into eax - // Move the RHS (denominator) into ebx - cg.emit.emit("pushl", rightReg); - cg.emit.emit("pushl", leftReg); - cg.emit.emit("popl", Register.EAX); - cg.emit.emit("popl", Register.EBX); - cg.emit.emitRaw("cltd"); // sign-extend %eax into %edx - cg.emit.emit("idivl", Register.EBX); // division, result into edx:eax - - // Move the result into the LHS, and pop off anything we saved - if (ast.operator == BinaryOp.BOp.B_DIV) - cg.emit.emitMove(Register.EAX, leftReg); - else - cg.emit.emitMove(Register.EDX, leftReg); - for (int i = affected.length - 1; i >= 0; i--) { - Register s = affected[i]; - if (!dontBother.contains(s) && cg.rm.isInUse(s)) - cg.emit.emit("popl", s); - } - break; - case B_AND: - cg.emit.emit("and", rightReg, leftReg); - break; - case B_OR: - cg.emit.emit("or", rightReg, leftReg); - break; - case B_EQUAL: // a == b <--> ! (a != b) - cg.emit.emit("xor", rightReg, leftReg); - // if 'leftReg'==0, set leftReg to '1' - String equal = cg.emit.uniqueLabel(); - String end = cg.emit.uniqueLabel(); - cg.emit.emit("cmp", 0, leftReg); - cg.emit.emit("je", equal); - cg.emit.emitMove(AssemblyEmitter.constant(0), leftReg); - cg.emit.emit("jmp", end); - cg.emit.emitLabel(equal); - cg.emit.emitMove(TRUE, leftReg); - cg.emit.emitLabel(end); - break; - case B_NOT_EQUAL: - String skipTrue = cg.emit.uniqueLabel(); - cg.emit.emit("xor", rightReg, leftReg); - cg.emit.emit("cmp", 0, leftReg); - cg.emit.emit("je", skipTrue); - cg.emit.emitMove(TRUE, leftReg); - cg.emit.emitLabel(skipTrue); - break; - default: // Comparison operations - String endLabel = cg.emit.uniqueLabel(); - cg.emit.emit("cmp", rightReg, leftReg); - // leftReg - rightReg - cg.emit.emitMove(TRUE, leftReg); + @Override + public Register unaryOp(UnaryOp ast, Void arg) { + { + Register argReg = gen(ast.arg()); switch (ast.operator) { - case B_LESS_THAN: - cg.emit.emit("jl", endLabel); - break; - case B_LESS_OR_EQUAL: - cg.emit.emit("jle", endLabel); - break; - case B_GREATER_THAN: - cg.emit.emit("jg", endLabel); - break; - case B_GREATER_OR_EQUAL: - cg.emit.emit("jge", endLabel); - break; - default: - throw new RuntimeException("A binary operation wasn't implemented"); + case U_PLUS: + break; + + case U_MINUS: + cg.emit.emit("negl", argReg); + break; + + case U_BOOL_NOT: + cg.emit.emit("xor", 1, argReg); + break; } - cg.emit.emitMove(FALSE, leftReg); - cg.emit.emitLabel(endLabel); + return argReg; + } + } +} + +/* + * This is the subclass of ExprGenerator containing the reference solution + */ +class ExprGeneratorRef extends ExprGenerator { + + /* cg and cgRef are the same instance. cgRef simply + * provides a wider interface */ + protected final AstCodeGeneratorRef cgRef; + + ExprGeneratorRef(AstCodeGeneratorRef astCodeGenerator) { + super(astCodeGenerator); + this.cgRef = astCodeGenerator; + } + + /** + * This routine handles register shortages. It generates a value for + * {@code right}, while keeping the value in {@code leftReg} live. However, + * if there are insufficient registers, it may temporarily store the value + * in {@code leftReg} to the stack. In this case, it will be restored into + * another register once {@code right} has been evaluated, but the register + * may not be the same as {@code leftReg}. Therefore, this function returns + * a pair of registers, the first of which stores the left value, and the + * second of which stores the right value. + */ + public Pair genPushing(Register leftReg, Expr right) { + Register newLeftReg = leftReg; + boolean pop = false; + + if (cgRef.rnv.calc(right) > cgRef.rm.availableRegisters()) { + cgRef.push(newLeftReg.repr); + cgRef.rm.releaseRegister(newLeftReg); + pop = true; } - cg.rm.releaseRegister(rightReg); + Register rightReg = gen(right); + + if (pop) { + newLeftReg = cgRef.rm.getRegister(); + cgRef.pop(newLeftReg.repr); + } + + return new Pair(newLeftReg, rightReg); + + } + + @Override + public Register binaryOp(BinaryOp ast, Void arg) { + Register leftReg = null; + Register rightReg = null; + + { + + leftReg = gen(ast.left()); + Pair regs = genPushing(leftReg, ast.right()); + leftReg = regs.a; + rightReg = regs.b; + + } + + assert leftReg != null && rightReg != null; + + new OperandsDispatcher() { + + @Override + public void booleanOp(Register leftReg, BOp op, Register rightReg) { + integerOp(leftReg, op, rightReg); + } + + @Override + public void integerOp(Register leftReg, BOp op, Register rightReg) { + + switch (op) { + case B_TIMES: + cgRef.emit.emit("imull", rightReg, leftReg); + break; + case B_PLUS: + cgRef.emit.emit("addl", rightReg, leftReg); + break; + case B_MINUS: + cgRef.emit.emit("subl", rightReg, leftReg); + break; + case B_DIV: + emitDivMod(Register.EAX, leftReg, rightReg); + break; + case B_MOD: + emitDivMod(Register.EDX, leftReg, rightReg); + break; + case B_AND: + cgRef.emit.emit("andl", rightReg, leftReg); + break; + case B_OR: + cgRef.emit.emit("orl", rightReg, leftReg); + break; + case B_EQUAL: + emitCmp("sete", leftReg, rightReg); + break; + case B_NOT_EQUAL: + emitCmp("setne", leftReg, rightReg); + break; + case B_LESS_THAN: + emitCmp("setl", leftReg, rightReg); + break; + case B_LESS_OR_EQUAL: + emitCmp("setle", leftReg, rightReg); + break; + case B_GREATER_THAN: + emitCmp("setg", leftReg, rightReg); + break; + case B_GREATER_OR_EQUAL: + emitCmp("setge", leftReg, rightReg); + break; + default: + throw new AssemblyFailedException( + "Invalid binary operator for " + + PrimitiveTypeSymbol.intType + " or " + + PrimitiveTypeSymbol.booleanType); + } + + } + + }.binaryOp(ast, leftReg, rightReg); + + cgRef.rm.releaseRegister(rightReg); return leftReg; } + private void emitCmp(String opname, Register leftReg, Register rightReg) { + + cgRef.emit.emit("cmpl", rightReg, leftReg); + + if (leftReg.hasLowByteVersion()) { + cgRef.emit.emit("movl", "$0", leftReg); + cgRef.emit.emit(opname, leftReg.lowByteVersion().repr); + } else { + cgRef.push(Register.EAX.repr); + cgRef.emit.emit("movl", "$0", Register.EAX); + cgRef.emit.emit(opname, "%al"); + cgRef.emit.emit("movl", Register.EAX, leftReg); + cgRef.pop(Register.EAX.repr); + } + + } + + private void emitDivMod(Register whichResultReg, Register leftReg, + Register rightReg) { + + // Compare right reg for 0 + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(rightReg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NON_ZERO); + cgRef.emitCallSuffix(null, 1, padding); + + // Save EAX, EBX, and EDX to the stack if they are not used + // in this subtree (but are used elsewhere). We will be + // changing them. + List dontBother = Arrays.asList(rightReg, leftReg); + Register[] affected = {Register.EAX, Register.EBX, Register.EDX}; + for (Register s : affected) + if (!dontBother.contains(s) && cgRef.rm.isInUse(s)) + cgRef.emit.emit("pushl", s); + + // Move the LHS (numerator) into eax + // Move the RHS (denominator) into ebx + cgRef.emit.emit("pushl", rightReg); + cgRef.emit.emit("pushl", leftReg); + cgRef.emit.emit("popl", Register.EAX); + cgRef.emit.emit("popl", "%ebx"); + cgRef.emit.emitRaw("cltd"); // sign-extend %eax into %edx + cgRef.emit.emit("idivl", "%ebx"); // division, result into edx:eax + + // Move the result into the LHS, and pop off anything we saved + cgRef.emit.emit("movl", whichResultReg, leftReg); + for (int i = affected.length - 1; i >= 0; i--) { + Register s = affected[i]; + if (!dontBother.contains(s) && cgRef.rm.isInUse(s)) + cgRef.emit.emit("popl", s); + } + } + @Override - public Register booleanConst(BooleanConst ast, Location arg) { - Register reg = cg.rm.getRegister(); - cg.emit.emitMove(ast.value ? TRUE : FALSE, reg); + public Register booleanConst(BooleanConst ast, Void arg) { + Register reg = cgRef.rm.getRegister(); + cgRef.emit.emit("movl", ast.value ? TRUE : FALSE, reg); return reg; } @Override - public Register builtInRead(BuiltInRead ast, Location arg) { - Register reg = cg.rm.getRegister(); - cg.emit.emit("sub", 16, STACK_REG); - cg.emit.emit("leal", AssemblyEmitter.registerOffset(8, STACK_REG), reg); - cg.emit.emitStore(reg, 4, STACK_REG); - cg.emit.emitStore("$STR_D", 0, STACK_REG); - cg.emit.emit("call", SCANF); - cg.emit.emitLoad(8, STACK_REG, reg); - cg.emit.emit("add", 16, STACK_REG); - return reg; - } - - /** - * A Cast from one type to another: {@code (typeName)arg} - */ - @Override - public Register cast(Cast ast, Location arg) { - // 1. Obtain a register with the desired type's address - Register desiredType = cg.rm.getRegister(); - cg.emit.emit("lea", Label.type(ast.type), desiredType); // lea copies the label's address to the reg - - // 2. Get a reference to the object to be casted, and push a copy for later - Register runtimeType = visit(ast.arg(), arg); - cg.emit.emit("push", runtimeType); - - // 3. Go to the type's vtable - cg.emit.emitLoad(0, runtimeType, runtimeType); - - // 4. Runtime type check: recursively go to superType until - String matchLabel = cg.emit.uniqueLabel(); - String checkSuperTypeLabel = cg.emit.uniqueLabel(); - cg.emit.emitLabel(checkSuperTypeLabel); - cg.emit.emit("cmpl", desiredType, runtimeType); - cg.emit.emit("je", matchLabel); // 4.1 It matches: ok - cg.emit.emitLoad(0, runtimeType, runtimeType); // Go to superclass - - cg.emit.emit("cmp", 0, runtimeType); // 4.2 null is reached (super type of Object): error - cg.emit.emit("jne", checkSuperTypeLabel); - Interrupts.exit(cg, ExitCode.INVALID_DOWNCAST); - cg.emit.emitLabel(matchLabel); - - cg.rm.releaseRegister(desiredType); - - // 5. Recover pointer to object and return it - cg.emit.emit("pop", runtimeType); - return runtimeType; - } - - @Override - public Register index(Index ast, Location arg) { - boolean obtainReference = arg.isObtainReference(); - String invalidBoundLabel = cg.emit.uniqueLabel(); - String validBoundLabel = cg.emit.uniqueLabel(); - String checkBoundsLabel = cg.emit.uniqueLabel(); - - // Obtain pointer to array and index to be accessed - Register pointer = visit(ast.left(), arg); - Register index = visit(ast.right(), arg); - - // check for null pointer - cg.emit.emit("cmp", 0, pointer); - cg.emit.emit("jne", checkBoundsLabel); // 0 != array - Interrupts.exit(cg, ExitCode.NULL_POINTER); - - // check if index>=0 and index index - cg.emit.emit("cmp", index, arraySizeReg); - cg.emit.emit("jg", validBoundLabel); // index < array.length - cg.emit.emitLabel(invalidBoundLabel); - Interrupts.exit(cg, ExitCode.INVALID_ARRAY_BOUNDS); - cg.rm.releaseRegister(arraySizeReg); - - // return array element (base + 2 * VAR_SIZE + index * VAR_SIZE) - cg.emit.emitLabel(validBoundLabel); - cg.emit.emit("imul", VAR_SIZE, index); - cg.emit.emit("add", pointer, index); - cg.rm.releaseRegister(pointer); - if (!obtainReference) - cg.emit.emitLoad(2 * VAR_SIZE, index, index); - else - cg.emit.emit("add", 2 * VAR_SIZE, index); - return index; - } - - @Override - public Register intConst(IntConst ast, Location arg) { - Register reg = cg.rm.getRegister(); - cg.emit.emitMove(ast.value, reg); + public Register builtInRead(BuiltInRead ast, Void arg) { + Register reg = cgRef.rm.getRegister(); + int padding = cgRef.emitCallPrefix(reg, 0); + cgRef.emit.emit("call", AstCodeGeneratorRef.READ_INTEGER); + cgRef.emitCallSuffix(reg, 0, padding); return reg; } @Override - public Register field(Field ast, Location arg) { - boolean obtainReference = arg.isObtainReference(); - String accessFieldLabel = cg.emit.uniqueLabel(); - Register pointer = visit(ast.arg(), arg); - cg.emit.emit("cmp", 0, pointer); - cg.emit.emit("jne", accessFieldLabel); - Interrupts.exit(cg, ExitCode.NULL_POINTER); - - cg.emit.emitLabel(accessFieldLabel); - String foundFieldLabel = cg.emit.uniqueLabel(); - String lookForFieldLabel = cg.emit.uniqueLabel(); - cg.emit.emit("add", VAR_SIZE, pointer); - cg.emit.emitLabel(lookForFieldLabel); - cg.emit.emit("cmpl", ast.sym.hashCode(), AssemblyEmitter.registerOffset(0, pointer)); - cg.emit.emit("je", foundFieldLabel); - cg.emit.emit("add", 2 * VAR_SIZE, pointer); - cg.emit.emit("jmp", lookForFieldLabel); - cg.emit.emitLabel(foundFieldLabel); - if (obtainReference) - cg.emit.emit("add", VAR_SIZE, pointer); - else - cg.emit.emitLoad(VAR_SIZE, pointer, pointer); - return pointer; - } - - /** - * Array structure: - *
    - *
  • Type of array (for casts, assignments and equalities)
  • - *
  • Size of array (N)
  • - *
  • element 0 or pointer to element 0
  • - *
  • ...
  • - *
  • element N or pointer to element N
  • - *
- * The type of the elements is stored in the vtable for the array (in order to - * save space).
- * - * The pointer that references the array points to the type of the array. - * The reason for this is so that all reference types have at pointer + 0 - * the type, for dynamic type comparisons for assignments, casts and ==/!= operators - */ - @Override - public Register newArray(NewArray ast, Location arg) { - String validArraySizeLabel = cg.emit.uniqueLabel(); - - // Check size of array is positive - Register arraySizeReg = visit(ast.arg(), arg); - cg.emit.emit("cmp", 0, arraySizeReg); - cg.emit.emit("jns", validArraySizeLabel); // size >= 0 - Interrupts.exit(cg, ExitCode.INVALID_ARRAY_SIZE); - - // Reserve for length + 2 variables - cg.emit.emitLabel(validArraySizeLabel); - cg.emit.emit("push", arraySizeReg); - cg.emit.emit("add", 2, arraySizeReg); - Register arrayPointerReg = calloc(arraySizeReg); - - // Store overhead information - // Type reference - Register arrayTypeReg = cg.rm.getRegister(); - cg.emit.emit("lea", Label.type(ast.type), arrayTypeReg); - cg.emit.emitStore(arrayTypeReg, 0 * VAR_SIZE, arrayPointerReg); - cg.rm.releaseRegister(arrayTypeReg); - // Number of elements - Register numElemReg = cg.rm.getRegister(); - cg.emit.emit("pop", numElemReg); - cg.emit.emitStore(numElemReg, 1 * VAR_SIZE, arrayPointerReg); - cg.rm.releaseRegister(numElemReg); - - return arrayPointerReg; - } - - /** - * Structure of reference type objects (except arrays) - *
    - *
  • Pointer to type vtable
  • - *
  • hashCode0, field0
  • - *
  • ...
  • - *
  • hashCodeN, fieldN
  • - *
- * The object pointer points to the first element, and every element - * is of VAR_SIZE - */ - @Override - public Register newObject(NewObject ast, Location arg) { - ClassSymbol sym = (ClassSymbol) ast.type; - - // Obtain from type size of allocated object and allocate memory - int size = 1; - ClassSymbol auxSym = sym; - while (auxSym != ClassSymbol.objectType) { - size += auxSym.fields.size() * 2; - auxSym = auxSym.superClass; - } - Register sizeReg = cg.rm.getRegister(); - cg.emit.emitMove(size, sizeReg); - - Register pointer = calloc(sizeReg); - - // Store the pointer to the type vtable - Register auxReg = cg.rm.getRegister(); - cg.emit.emit("lea", Label.type(sym), auxReg); - cg.emit.emitStore(auxReg, 0, pointer); - cg.emit.emit("push", pointer); // Save the pointer to the beginning to return later - cg.rm.releaseRegister(auxReg); - - // Store the hashes for the fields' variable symbols - cg.emit.emit("add", VAR_SIZE, pointer); - auxSym = sym; - while (auxSym != ClassSymbol.objectType) { - for (VariableSymbol field : auxSym.fields.values()) { - cg.emit.emitStore(field.hashCode(), 0, pointer); - cg.emit.emit("add", 2 * VAR_SIZE, pointer); - } - auxSym = auxSym.superClass; - } - - // Recover the initial address and return - cg.emit.emit("pop", pointer); - return pointer; + public Register cast(Cast ast, Void arg) { + // Invoke the helper function. If it does not exit, + // the cast succeeded! + Register objReg = gen(ast.arg()); + int padding = cgRef.emitCallPrefix(null, 2); + cgRef.push(objReg.repr); + cgRef.push(AssemblyEmitter.labelAddress(cgRef.vtable(ast.type))); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_CAST); + cgRef.emitCallSuffix(null, 2, padding); + return objReg; } @Override - public Register nullConst(NullConst ast, Location arg) { - Register reg = cg.rm.getRegister(); - cg.emit.emitMove(0, reg); + public Register index(Index ast, Void arg) { + Register arr = gen(ast.left()); + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(arr.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL); + cgRef.emitCallSuffix(null, 1, padding); + Pair pair = genPushing(arr, ast.right()); + arr = pair.a; + Register idx = pair.b; + + // Check array bounds + padding = cgRef.emitCallPrefix(null, 2); + cgRef.push(idx.repr); + cgRef.push(arr.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_BOUNDS); + cgRef.emitCallSuffix(null, 2, padding); + + cgRef.emit.emitMove(AssemblyEmitter.arrayAddress(arr, idx), idx); + cgRef.rm.releaseRegister(arr); + return idx; + } + + @Override + public Register field(Field ast, Void arg) { + Register reg = gen(ast.arg()); + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(reg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL); + cgRef.emitCallSuffix(null, 1, padding); + assert ast.sym.offset != -1; + cgRef.emit.emitLoad(ast.sym.offset, reg, reg); return reg; } @Override - - public Register thisRef(ThisRef ast, Location arg) { - Register register = cg.rm.getRegister(); - cg.emit.emitLoad(2 * VAR_SIZE, BASE_REG, register); - return register; - } + public Register newArray(NewArray ast, Void arg) { + // Size of the array = 4 + 4 + elemsize * num elem. + // Compute that into reg, store it into the stack as + // an argument to Javali$Alloc(), and then use it to store final + // result. + ArrayTypeSymbol arrsym = (ArrayTypeSymbol) ast.type; + Register reg = gen(ast.arg()); + // Check for negative array sizes + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(reg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_SIZE); + cgRef.emitCallSuffix(null, 1, padding); - /** - * implementation of x86 calling convention according to: - * http://unixwiz.net/techtips/win32-callconv-asm.html - * following the __cdecl calling convention - */ - @Override - public Register methodCall(MethodCallExpr ast, Location arg) { - // 0. Save registers to stack - List callerSaved = new ArrayList<>(); - for (Register reg : CALLER_SAVE) { - if (cg.rm.isInUse(reg)) { - callerSaved.add(0, reg); - cg.emit.emit("push", Register.EAX); - } - } + Register lenReg = cgRef.rm.getRegister(); + cgRef.emit.emit("movl", reg, lenReg); // save length - // 1. Evaluate all the arguments left-to-right (according to Java's spec) - // and push them to the stack in right-to-left order. We will push them ltr - // and then swap them so that the order is reversed - for (Expr allArgument : ast.allArguments()) { - Register argumentRegister = visit(allArgument, arg); - cg.emit.emit("push", argumentRegister); - cg.rm.releaseRegister(argumentRegister); - } - for (int i = 0; i < ast.allArguments().size() / 2; i++) { - int offset1 = i * VAR_SIZE; - int offset2 = (ast.allArguments().size() - 1 - i) * VAR_SIZE; - Register aux1 = cg.rm.getRegister(); - Register aux2 = cg.rm.getRegister(); - cg.emit.emitLoad(offset1, STACK_REG, aux1); - cg.emit.emitLoad(offset2, STACK_REG, aux2); - cg.emit.emitStore(aux1, offset2, STACK_REG); - cg.emit.emitStore(aux2, offset1, STACK_REG); - cg.rm.releaseRegister(aux1); - cg.rm.releaseRegister(aux2); - } + cgRef.emit.emit("imul", Config.SIZEOF_PTR, reg); + cgRef.emit.emit("addl", 2 * Config.SIZEOF_PTR, reg); - // 2. Call function - // 2.1. Search for the method pointer using the hashCode - Register thisRef = cg.rm.getRegister(); - cg.emit.emitLoad(0, STACK_REG, thisRef); - int hashCode = ast.methodName.hashCode(); - String lookForMethod = cg.emit.uniqueLabel(); - String methodFound = cg.emit.uniqueLabel(); - cg.emit.emitLoad(0, thisRef, thisRef); // Go to type vtable - cg.emit.emit("add", 1 * VAR_SIZE, thisRef); // Skip the reference to superType - cg.emit.emitLabel(lookForMethod); - cg.emit.emit("cmpl", hashCode, AssemblyEmitter.registerOffset(0, thisRef)); - cg.emit.emit("je", methodFound); // hashCode == methodName.hashCode - cg.emit.emit("add", 2 * VAR_SIZE, thisRef); // Go to next hashCode - cg.emit.emit("jmp", lookForMethod); - cg.emit.emitLabel(methodFound); - // 2.2. Call the function - cg.emit.emit("call", AssemblyEmitter.registerOffset(VAR_SIZE, thisRef)); - cg.rm.releaseRegister(thisRef); + int allocPadding = cgRef.emitCallPrefix(reg, 1); + cgRef.push(reg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.ALLOC); + cgRef.emitCallSuffix(reg, 1, allocPadding); - // 3. Pop arguments from stack - cg.emit.emit("add", VAR_SIZE * ast.allArguments().size(), STACK_REG); + // store vtable ptr and array length + cgRef.emit.emitStore(AssemblyEmitter.labelAddress(cgRef.vtable(arrsym)), 0, reg); + cgRef.emit.emitStore(lenReg, Config.SIZEOF_PTR, reg); + cgRef.rm.releaseRegister(lenReg); - // 4. Return result to caller - Register result = null; - if (ast.sym.returnType != PrimitiveTypeSymbol.voidType) { - result = cg.rm.getRegister(); - cg.emit.emitMove(Register.EAX, result); - } - - // 5. Restore registers - for (Register reg : callerSaved) { - if (cg.rm.isInUse(reg)) - cg.emit.emit("pop", Register.EAX); - else - cg.emit.emit("add", 4, Register.ESP); // Don't overwrite - } - return result; + return reg; } @Override - public Register unaryOp(UnaryOp ast, Location arg) { - Register argReg = visit(ast.arg(), arg); - switch (ast.operator) { - case U_PLUS: - break; - - case U_MINUS: - cg.emit.emit("negl", argReg); - break; - - case U_BOOL_NOT: - cg.emit.emit("negl", argReg); - cg.emit.emit("incl", argReg); - break; - } - return argReg; + public Register newObject(NewObject ast, Void arg) { + ClassSymbol clssym = (ClassSymbol) ast.type; + Register reg = cgRef.rm.getRegister(); + int allocPadding = cgRef.emitCallPrefix(reg, 1); + cgRef.push(constant(clssym.sizeof)); + cgRef.emit.emit("call", AstCodeGeneratorRef.ALLOC); + cgRef.emitCallSuffix(reg, 1, allocPadding); + cgRef.emit.emitStore(labelAddress(cgRef.vtable(clssym)), 0, reg); + return reg; } - /** - * Obtains the value or reference to a variable described by a Var node - * and a VariableSymbol. - */ @Override - public Register var(Var ast, Location arg) { - boolean obtainReference = arg.isObtainReference(); - Register register = cg.rm.getRegister(); - cg.emit.emitMove(BASE_REG, register); - int offset; + public Register nullConst(NullConst ast, Void arg) { + Register reg = cgRef.rm.getRegister(); + cgRef.emit.emit("movl", "$0", reg); + return reg; + } + + @Override + public Register thisRef(ThisRef ast, Void arg) { + Register reg = cgRef.rm.getRegister(); + cgRef.emit.emitLoad(cgRef.THIS_OFFSET, BASE_REG, reg); + return reg; + } + + @Override + public Register methodCall(MethodCallExpr ast, Void arg) { + return cgRef.sg.methodCall(ast.sym, ast.allArguments()); + } + + @Override + public Register var(Var ast, Void arg) { + Register reg = cgRef.rm.getRegister(); switch (ast.sym.kind) { - case PARAM: - offset = 3 + arg.methodSym().parameters.indexOf(ast.sym); - break; case LOCAL: - offset = -(1 + positionOfLocal(arg.methodSym(), ast.name)); + case PARAM: + assert ast.sym.offset != -1; + cgRef.emit.emitLoad(ast.sym.offset, BASE_REG, reg); break; case FIELD: - String foundFieldLabel = cg.emit.uniqueLabel(); - String lookForFieldLabel = cg.emit.uniqueLabel(); - cg.emit.emitLoad(2 * VAR_SIZE, register, register); - cg.emit.emit("add", VAR_SIZE, register); - cg.emit.emitLabel(lookForFieldLabel); - cg.emit.emit("cmpl", ast.sym.hashCode(), AssemblyEmitter.registerOffset(0, register)); - cg.emit.emit("je", foundFieldLabel); - cg.emit.emit("add", 2 * VAR_SIZE, register); - cg.emit.emit("jmp", lookForFieldLabel); - cg.emit.emitLabel(foundFieldLabel); - offset = 1; // The next element will be the reference we want - break; - default: - throw new RuntimeException("VariableSymbol Kind option not implemented"); + // These are removed by the ExprRewriter added to the + // end of semantic analysis. + throw new RuntimeException("Should not happen"); } - if (obtainReference) - cg.emit.emit("add", offset * VAR_SIZE, register); - else - cg.emit.emitLoad(offset * VAR_SIZE, register, register); - return register; + return reg; } - private int positionOfLocal(MethodSymbol sym, String name) { - List locals = new ArrayList<>(sym.locals.keySet()); - locals.sort(Comparator.comparing(o -> o)); - for (int i = 0; i < locals.size(); i++) - if (locals.get(i).equals(name)) - return i; - throw new RuntimeException("The variable could not be found, and should be there"); - } - - /** - * Helper function that calls calloc to obtain memory for reference type variables - * @param numberOfElements Size in bytes, stored in a register - * @return A register with the first address of the memory allocated - */ - private Register calloc(Register numberOfElements) { - cg.emit.emitComment("calloc begin arg=" + numberOfElements); - String callocOk = cg.emit.uniqueLabel(); - - boolean eaxInUse = cg.rm.isInUse(Register.EAX); - if (eaxInUse && Register.EAX != numberOfElements) - cg.emit.emit("push", Register.EAX); - - // push arguments to the stack, then call calloc - // the size of allocated elements is always four bytes! - cg.emit.emit("push", AssemblyEmitter.constant(VAR_SIZE)); - cg.emit.emit("push", numberOfElements); - cg.rm.releaseRegister(numberOfElements); - cg.emit.emit("call", "calloc"); - cg.emit.emit("add", 8, STACK_REG); - - // Check for null pointer (if calloc fails) - cg.emit.emit("cmp", 0, Register.EAX); - cg.emit.emit("jne", callocOk); - Interrupts.exit(cg, ExitCode.INTERNAL_ERROR); - - cg.emit.emitLabel(callocOk); - Register result = cg.rm.getRegister(); - cg.emit.emitMove(Register.EAX, result); - - // pop EAX if needed - if (eaxInUse && Register.EAX != numberOfElements) - cg.emit.emit("pop", Register.EAX); - - cg.emit.emitComment("calloc end"); - return result; + @Override + public Register unaryOp(UnaryOp ast, Void arg) { + if (ast.operator == UOp.U_MINUS) { + Register argReg = gen(ast.arg()); + cgRef.emit.emit("negl", argReg); + return argReg; + } else { + return super.unaryOp(ast, arg); + } + } +} + +class ExprGeneratorNop90 extends ExprGeneratorRef { + protected final CfgCodeGenerator cfgCg; + protected CompileTimeEvaluator cte; + + ExprGeneratorNop90(AstCodeGeneratorNop90 cg, CfgCodeGenerator cfgCodeGenerator) { + super(cg); + this.cfgCg = cfgCodeGenerator; + cte = new CompileTimeEvaluator(); + } + + + @Override + public Register field(Field ast, Void arg) { + Register reg = gen(ast.arg()); + // only emit runtime Null check if the field is not known to be non null + if (!cfgCg.check.isNotNull(ast)) { + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(reg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL); + cgRef.emitCallSuffix(null, 1, padding); + } + assert ast.sym.offset != -1; + cgRef.emit.emitLoad(ast.sym.offset, reg, reg); + return reg; + } + + @Override + public Register index(Index ast, Void arg) { + boolean emitBoundCheck = cfgCg.check.checkArrayBound(ast); + boolean skipNullCheck = !cfgCg.check.isNotNull(ast.left()); + int padding; + + Register arr = gen(ast.left()); + if (skipNullCheck) { + padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(arr.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL); + cgRef.emitCallSuffix(null, 1, padding); + } + Pair pair = genPushing(arr, ast.right()); + arr = pair.a; + Register idx = pair.b; + + // Check array bounds + if (emitBoundCheck) { + padding = cgRef.emitCallPrefix(null, 2); + cgRef.push(idx.repr); + cgRef.push(arr.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_BOUNDS); + cgRef.emitCallSuffix(null, 2, padding); + } + cgRef.emit.emitMove(AssemblyEmitter.arrayAddress(arr, idx), idx); + cgRef.rm.releaseRegister(arr); + return idx; + } + + @Override + public Register newArray(NewArray ast, Void arg) { + // Size of the array = 4 + 4 + elemsize * num elem. + // Compute that into reg, store it into the stack as + // an argument to Javali$Alloc(), and then use it to store final + // result. + boolean checkArraySize = cfgCg.check.checkArraySize(ast); + ArrayTypeSymbol arrsym = (ArrayTypeSymbol) ast.type; + int size; + Register reg = cgRef.rm.getRegister(); + Register lenReg = cgRef.rm.getRegister(); + Optional sizeOptional = cte.calc(ast.arg()); + if (!sizeOptional.isPresent()) { + reg = gen(ast.arg());} + + // Check for negative array sizes + if (checkArraySize) { + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(reg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_SIZE); + cgRef.emitCallSuffix(null, 1, padding); + } + + if (!sizeOptional.isPresent()) { + cgRef.emit.emit("movl", reg, lenReg);} // save length} + else { + size = sizeOptional.get(); + cgRef.emit.emit("movl", size, lenReg); + } + + if (!sizeOptional.isPresent()) { + cgRef.emit.emit("imul", Config.SIZEOF_PTR, reg); + cgRef.emit.emit("addl", 2 * Config.SIZEOF_PTR, reg);} + else { + size = sizeOptional.get(); + size = (size * Config.SIZEOF_PTR) + (2*Config.SIZEOF_PTR); + cgRef.emit.emit("pushl", size); + } + + int allocPadding = cgRef.emitCallPrefix(reg, 1); + if (!sizeOptional.isPresent()) { + cgRef.push(reg.repr);} + cgRef.emit.emit("call", AstCodeGeneratorRef.ALLOC); + cgRef.emitCallSuffix(reg, 1, allocPadding); + + // store vtable ptr and array length + cgRef.emit.emitStore(AssemblyEmitter.labelAddress(cgRef.vtable(arrsym)), 0, reg); + cgRef.emit.emitStore(lenReg, Config.SIZEOF_PTR, reg); + cgRef.rm.releaseRegister(lenReg); + + return reg; + } + + + @Override + public Register unaryOp(UnaryOp ast, Void arg) { + Optional optinalvalue = cte.calc(ast.arg()); + Register argReg; + if (optinalvalue.isPresent()) { + int value = optinalvalue.get(); + argReg = cgRef.rm.getRegister(); + switch (ast.operator) { + case U_PLUS: + break; + + case U_MINUS: + value = value * -1; + cgRef.emit.emit("movl",value, argReg); + break; + case U_BOOL_NOT: + if (value==1) { + cgRef.emit.emit("movl",0, argReg); + } + else { + cgRef.emit.emit("movl",1, argReg); + } + break; + } + return argReg; + } + + argReg = gen(ast.arg()); + switch (ast.operator) { + case U_PLUS: + break; + + case U_MINUS: + cgRef.emit.emit("negl", argReg); + break; + + case U_BOOL_NOT: + cgRef.emit.emit("xor", 1, argReg); + break; + } + return argReg; + } + + + + @Override + public Register binaryOp(BinaryOp ast, Void arg) { + Register leftReg; + Register rightReg = null; + Optional leftVal = cte.calc(ast.left()); + Optional rightVal = cte.calc(ast.right()); + + if (leftVal.isPresent() && ast.operator.isCommutative()) { + // Switch sides of operation so that rightVal has the value + Expr auxExpr = ast.left(); + ast.setLeft(ast.right()); + ast.setRight(auxExpr); + rightVal = leftVal; + } + + leftReg = gen(ast.left()); + + if (!rightVal.isPresent()) { + switch (ast.operator) { + case B_OR: + case B_AND: + shortCircuitedBinaryOp(leftReg, ast.operator, ast.right()); + return leftReg; + default: + Pair regs = genPushing(leftReg, ast.right()); + leftReg = regs.a; + rightReg = regs.b; + } + } + + assert leftReg != null && (rightReg != null || rightVal.isPresent()); + + ImmediateOperandsDispatcher dispatcher = new ImmediateOperandsDispatcher() { + + @Override + public void booleanOp(Register leftReg, BOp op, Register rightReg) { + integerOp(leftReg, op, rightReg); + } + + @Override + public void booleanOp(Register leftReg, BOp op, int rightVal) { + integerOp(leftReg, op, rightVal); + } + + @Override + public void integerOp(Register leftReg, BOp op, int rightVal) { + switch(op) { + case B_TIMES: + cgRef.emit.emit("imull", rightVal, leftReg); + break; + case B_PLUS: + cgRef.emit.emit("addl", rightVal, leftReg); + break; + case B_MINUS: + cgRef.emit.emit("subl", rightVal, leftReg); + break; + case B_DIV: + emitDivMod(Register.EAX, leftReg, rightVal); + break; + case B_MOD: + emitDivMod(Register.EDX, leftReg, rightVal); + break; + case B_AND: + cgRef.emit.emit("andl", rightVal, leftReg); + break; + case B_OR: + cgRef.emit.emit("orl", rightVal, leftReg); + break; + case B_EQUAL: + emitCmp("sete", leftReg, rightVal); + break; + case B_NOT_EQUAL: + emitCmp("setne", leftReg, rightVal); + break; + case B_LESS_THAN: + emitCmp("setl", leftReg, rightVal); + break; + case B_LESS_OR_EQUAL: + emitCmp("setle", leftReg, rightVal); + break; + case B_GREATER_THAN: + emitCmp("setg", leftReg, rightVal); + break; + case B_GREATER_OR_EQUAL: + emitCmp("setge", leftReg, rightVal); + break; + default: + throw new AssemblyFailedException( + "Invalid binary operator for " + + PrimitiveTypeSymbol.intType + " or " + + PrimitiveTypeSymbol.booleanType); + } + } + + @Override + public void integerOp(Register leftReg, BOp op, Register rightReg) { + + switch (op) { + case B_TIMES: + cgRef.emit.emit("imull", rightReg, leftReg); + break; + case B_PLUS: + cgRef.emit.emit("addl", rightReg, leftReg); + break; + case B_MINUS: + cgRef.emit.emit("subl", rightReg, leftReg); + break; + case B_DIV: + emitDivMod(Register.EAX, leftReg, rightReg); + break; + case B_MOD: + emitDivMod(Register.EDX, leftReg, rightReg); + break; + case B_AND: + cgRef.emit.emit("andl", rightReg, leftReg); + break; + case B_OR: + cgRef.emit.emit("orl", rightReg, leftReg); + break; + case B_EQUAL: + emitCmp("sete", leftReg, rightReg); + break; + case B_NOT_EQUAL: + emitCmp("setne", leftReg, rightReg); + break; + case B_LESS_THAN: + emitCmp("setl", leftReg, rightReg); + break; + case B_LESS_OR_EQUAL: + emitCmp("setle", leftReg, rightReg); + break; + case B_GREATER_THAN: + emitCmp("setg", leftReg, rightReg); + break; + case B_GREATER_OR_EQUAL: + emitCmp("setge", leftReg, rightReg); + break; + default: + throw new AssemblyFailedException( + "Invalid binary operator for " + + PrimitiveTypeSymbol.intType + " or " + + PrimitiveTypeSymbol.booleanType); + } + } + }; + + if (rightVal.isPresent()) { + dispatcher.binaryOp(ast, leftReg, rightVal.get()); + } else { + dispatcher.binaryOp(ast, leftReg, rightReg); + cgRef.rm.releaseRegister(rightReg); + } + return leftReg; + } + + private void shortCircuitedBinaryOp(Register left, BOp op, Expr right) { + int definitiveValue; + String opStr; + switch (op) { + case B_AND: + definitiveValue = FALSE; + opStr = "andl"; + break; + case B_OR: + definitiveValue = TRUE; + opStr = "orl"; + break; + default: + throw new AssemblyFailedException("Unimplemented operator " + op); + } + String skipRightLabel = cgRef.emit.uniqueLabel(); + // Skip if value is unchangeable by operation + cgRef.emit.emit("cmp", definitiveValue, left); + cgRef.emit.emit("je", skipRightLabel); + // Generate right side + Pair regs = genPushing(left, right); + left = regs.a; + Register rightReg = regs.b; + // Compare + cgRef.emit.emit(opStr, rightReg, left); + // Skip label + cgRef.emit.emitLabel(skipRightLabel); + } + + private void emitDivMod(Register whichResultReg, Register leftReg, int rightImm) { + if (rightImm == 0) { + cgRef.emit.emit("pushl", ExitCode.DIVISION_BY_ZERO.value); + cgRef.emit.emit("call", Config.EXIT); + return; + } + + // Save EAX, EBX, and EDX to the stack if they are not used + // in this subtree (but are used elsewhere). We will be + // changing them. + List dontBother = Collections.singletonList(leftReg); + Register[] affected = {Register.EAX, Register.EBX, Register.EDX}; + for (Register s : affected) + if (!dontBother.contains(s) && cgRef.rm.isInUse(s)) + cgRef.emit.emit("pushl", s); + + // Move the LHS (numerator) into eax + // Move the RHS (denominator) into ebx + cgRef.emit.emit("movl", leftReg, Register.EAX); + cgRef.emit.emit("movl", rightImm, Register.EBX); + cgRef.emit.emitRaw("cltd"); // sign-extend %eax into %edx + cgRef.emit.emit("idivl", Register.EBX); // division, result into edx:eax + + // Move the result into the LHS, and pop off anything we saved + cgRef.emit.emit("movl", whichResultReg, leftReg); + for (int i = affected.length - 1; i >= 0; i--) { + Register s = affected[i]; + if (!dontBother.contains(s) && cgRef.rm.isInUse(s)) + cgRef.emit.emit("popl", s); + } + } + + private void emitDivMod(Register whichResultReg, Register leftReg, Register rightReg) { + // Compare right reg for 0 if value of rightReg is not known at compiletime + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(rightReg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NON_ZERO); + cgRef.emitCallSuffix(null, 1, padding); + + // Save EAX, EBX, and EDX to the stack if they are not used + // in this subtree (but are used elsewhere). We will be + // changing them. + List dontBother = Arrays.asList(rightReg, leftReg); + Register[] affected = {Register.EAX, Register.EBX, Register.EDX}; + for (Register s : affected) + if (!dontBother.contains(s) && cgRef.rm.isInUse(s)) + cgRef.emit.emit("pushl", s); + + // Move the LHS (numerator) into eax + // Move the RHS (denominator) into ebx + cgRef.emit.emit("pushl", rightReg); + cgRef.emit.emit("pushl", leftReg); + cgRef.emit.emit("popl", Register.EAX); + cgRef.emit.emit("popl", "%ebx"); + cgRef.emit.emitRaw("cltd"); // sign-extend %eax into %edx + cgRef.emit.emit("idivl", "%ebx"); // division, result into edx:eax + + // Move the result into the LHS, and pop off anything we saved + cgRef.emit.emit("movl", whichResultReg, leftReg); + for (int i = affected.length - 1; i >= 0; i--) { + Register s = affected[i]; + if (!dontBother.contains(s) && cgRef.rm.isInUse(s)) + cgRef.emit.emit("popl", s); + } + } + + // Comparison emission split into 3 to allow immediate values + + private void emitCmp(String opname, Register leftReg, Register rightReg) { + cgRef.emit.emit("cmpl", rightReg, leftReg); + emitCmp(opname, leftReg); + } + + private void emitCmp(String opname, Register leftReg, int rightImm) { + cgRef.emit.emit("cmpl", rightImm, leftReg); + emitCmp(opname, leftReg); + } + + private void emitCmp(String opname, Register register) { + if (register.hasLowByteVersion()) { + cgRef.emit.emit("movl", "$0", register); + cgRef.emit.emit(opname, register.lowByteVersion().repr); + } else { + if (cgRef.rm.isInUse(Register.EAX)) + cgRef.push(Register.EAX.repr); + cgRef.emit.emit("movl", "$0", Register.EAX); + cgRef.emit.emit(opname, "%al"); + cgRef.emit.emit("movl", Register.EAX, register); + if (cgRef.rm.isInUse(Register.EAX)) + cgRef.pop(Register.EAX.repr); + } + } + + + @Override + public Register cast(Cast ast, Void arg) { + Register objReg = gen(ast.arg()); + // Skip error checking if up-cast or dynamic type is known + if (isUpCast(ast) || dynamicTypeKnown(ast)) + return objReg; + + int padding = cgRef.emitCallPrefix(null, 2); + cgRef.push(objReg.repr); + cgRef.push(AssemblyEmitter.labelAddress(cgRef.vtable(ast.type))); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_CAST); + cgRef.emitCallSuffix(null, 2, padding); + return objReg; + } + + private boolean isUpCast(Cast cast) { + return isUpCast(cast.type, cast.arg().type); + } + + + private boolean isUpCast(TypeSymbol to, TypeSymbol from) { + if (from instanceof ClassSymbol) { + // Search upwards to find a matching type + ClassSymbol sym = (ClassSymbol) from; + while (sym != ClassSymbol.objectType) { + if (sym == to) + return true; + sym = sym.superClass; + } + } else { + assert from instanceof ArrayTypeSymbol; + // Nothing to do, as only superType is Object + } + // Casts to Object and to the same declared type always succeed + return to == from || to == ClassSymbol.objectType; + } + + private boolean dynamicTypeKnown(Cast cast) { + assert cfgCg.dynamicTypeState != null; + if (cast.arg() instanceof Var) { + VariableSymbol symbol = ((Var) cast.arg()).sym; + MaybeC dynamicType = cfgCg.dynamicTypeState.get(symbol); + if (dynamicType != null && dynamicType.isConstant()) + return isUpCast(cast.type, dynamicType.getValue()); + } + return false; + } +} + +/* Dispatches BinaryOp based on the types of the operands + */ +abstract class OperandsDispatcher { + + public abstract void integerOp(Register leftReg, BOp op, + Register rightReg); + + public abstract void booleanOp(Register leftReg, BOp op, + Register rightReg); + + public void binaryOp(BinaryOp ast, Register leftReg, Register rightReg) { + + assert ast.type != null; + + if (ast.type == PrimitiveTypeSymbol.intType) { + integerOp(leftReg, ast.operator, rightReg); + } else if (ast.type == PrimitiveTypeSymbol.booleanType) { + + final TypeSymbol opType = ast.left().type; + + if (opType == PrimitiveTypeSymbol.intType) { + integerOp(leftReg, ast.operator, rightReg); + } else if (opType == PrimitiveTypeSymbol.booleanType) { + booleanOp(leftReg, ast.operator, rightReg); + } else { + integerOp(leftReg, ast.operator, rightReg); + } + } + } +} + +abstract class ImmediateOperandsDispatcher extends OperandsDispatcher { + + public abstract void integerOp(Register leftReg, BOp op, int rightVal); + + public abstract void booleanOp(Register leftReg, BOp op, int rightVal); + + public void binaryOp(BinaryOp ast, Register leftReg, int rightVal) { + assert ast.type != null; + + if (ast.type == PrimitiveTypeSymbol.intType) { + integerOp(leftReg, ast.operator, rightVal); + } else if (ast.type == PrimitiveTypeSymbol.booleanType) { + final TypeSymbol opType = ast.left().type; + if (opType == PrimitiveTypeSymbol.intType) { + integerOp(leftReg, ast.operator, rightVal); + } else if (opType == PrimitiveTypeSymbol.booleanType) { + booleanOp(leftReg, ast.operator, rightVal); + } else { + booleanOp(leftReg, ast.operator, rightVal); + } + } } } diff --git a/src/cd/backend/codegen/Interrupts.java b/src/cd/backend/codegen/Interrupts.java deleted file mode 100644 index 0e37098..0000000 --- a/src/cd/backend/codegen/Interrupts.java +++ /dev/null @@ -1,19 +0,0 @@ -package cd.backend.codegen; - -import cd.backend.ExitCode; -import cd.backend.codegen.RegisterManager.Register; - -public class Interrupts { - protected static final int INTERRUPT_EXIT = 1; - - /** - * Generates a exit interrupt with the code provided - * @param cg AstCodeGenerator to print instructions - * @param exitCode Number to use as exit code (can use constants in this class) - */ - protected static void exit(AstCodeGenerator cg, ExitCode exitCode) { - cg.emit.emitMove(AssemblyEmitter.constant(INTERRUPT_EXIT), Register.EAX); - cg.emit.emitMove(AssemblyEmitter.constant(exitCode.value), Register.EBX); - cg.emit.emit("int", 0x80); - } -} diff --git a/src/cd/backend/codegen/Location.java b/src/cd/backend/codegen/Location.java deleted file mode 100644 index 9eea74b..0000000 --- a/src/cd/backend/codegen/Location.java +++ /dev/null @@ -1,42 +0,0 @@ -package cd.backend.codegen; - -import cd.ir.Symbol.ClassSymbol; -import cd.ir.Symbol.MethodSymbol; - -public class Location { - private ClassSymbol classSymbol; - private MethodSymbol methodSymbol = null; - private boolean obtainReference = false; - - public Location (ClassSymbol sym) { - classSymbol = sym; - } - - public ClassSymbol classSym() { - return classSymbol; - } - - public MethodSymbol methodSym() { - assert methodSymbol != null; - return methodSymbol; - } - - public void enterMethod(MethodSymbol sym) { - methodSymbol = sym; - } - - public void leaveMethod() { - methodSymbol = null; - } - - public boolean isObtainReference() { - boolean aux = obtainReference; - obtainReference = false; - return aux; - } - - public Location obtainReference() { - obtainReference = true; - return this; - } -} diff --git a/src/cd/backend/codegen/RegisterManager.java b/src/cd/backend/codegen/RegisterManager.java index 36852b9..73d6e0a 100644 --- a/src/cd/backend/codegen/RegisterManager.java +++ b/src/cd/backend/codegen/RegisterManager.java @@ -16,10 +16,10 @@ public class RegisterManager { Register.EDI, Register.EBX}; public static final Register CALLER_SAVE[] = new Register[]{Register.EAX, Register.ECX, Register.EDX}; - + // list of general purpose registers public static final Register GPR[] = new Register[]{Register.EAX, Register.EBX, - Register.ECX, Register.EDX, Register.ESI, Register.EDI}; + Register.ECX, Register.EDX, Register.ESI, Register.EDI}; // special purpose registers public static final Register BASE_REG = Register.EBP; @@ -27,7 +27,7 @@ public class RegisterManager { public static final int SIZEOF_REG = 4; - + public enum Register { EAX("%eax", ByteRegister.EAX), EBX("%ebx", ByteRegister.EBX), ECX( "%ecx", ByteRegister.ECX), EDX("%edx", ByteRegister.EDX), ESI( @@ -99,18 +99,11 @@ public class RegisterManager { return registers.remove(last); } - public void useRegister(Register reg) { - assert registers.contains(reg); - assert reg != null; - registers.remove(reg); - } - /** * marks a currently used register as free */ public void releaseRegister(Register reg) { assert !registers.contains(reg); - assert reg != null; registers.add(reg); } diff --git a/src/cd/backend/codegen/RegsNeededVisitor.java b/src/cd/backend/codegen/RegsNeededVisitor.java index 1edef7f..ac386be 100644 --- a/src/cd/backend/codegen/RegsNeededVisitor.java +++ b/src/cd/backend/codegen/RegsNeededVisitor.java @@ -10,18 +10,19 @@ import java.util.Map; import static java.lang.Math.max; import static java.lang.Math.min; -/** - * Determines the maximum number of registers - * required to execute one subtree. */ +/** + * Determines the maximum number of registers + * required to execute one subtree. + */ public class RegsNeededVisitor extends AstVisitor { - + public int calc(Ast ast) { return visit(ast, null); } - - private Map memo = new HashMap(); - - /** + + private Map memo = new HashMap(); + + /** * Override visit() so as to memorize the results and avoid * unnecessary computation */ @@ -31,9 +32,9 @@ public class RegsNeededVisitor extends AstVisitor { return memo.get(ast); Integer res = ast.accept(this, null); memo.put(ast, res); - return res; + return res; } - + @Override protected Integer dflt(Ast ast, Void arg) { // For a non-expression, it suffices to find the @@ -54,8 +55,8 @@ public class RegsNeededVisitor extends AstVisitor { public Integer binaryOp(BinaryOp ast, Void arg) { int left = calc(ast.left()); int right = calc(ast.right()); - int ifLeftFirst = max(left, right+1); - int ifRightFirst = max(left+1, right); + int ifLeftFirst = max(left, right + 1); + int ifRightFirst = max(left + 1, right); int overall = min(ifLeftFirst, ifRightFirst); return overall; } @@ -64,7 +65,7 @@ public class RegsNeededVisitor extends AstVisitor { public Integer assign(Assign ast, Void arg) { return max(calc(ast.left()) + 1, calc(ast.right())); } - + @Override public Integer booleanConst(BooleanConst ast, Void arg) { return 1; @@ -74,7 +75,7 @@ public class RegsNeededVisitor extends AstVisitor { public Integer builtInRead(BuiltInRead ast, Void arg) { return 1; } - + @Override public Integer cast(Cast ast, Void arg) { return calc(ast.arg()); @@ -84,7 +85,7 @@ public class RegsNeededVisitor extends AstVisitor { public Integer index(Index ast, Void arg) { return max(calc(ast.left()), calc(ast.right()) + 1); } - + @Override public Integer field(Field ast, Void arg) { return calc(ast.arg()); @@ -114,7 +115,7 @@ public class RegsNeededVisitor extends AstVisitor { public Integer thisRef(ThisRef ast, Void arg) { return 1; } - + @Override public Integer methodCall(MethodCallExpr ast, Void arg) { int max = 1; @@ -134,5 +135,5 @@ public class RegsNeededVisitor extends AstVisitor { @Override public Integer var(Var ast, Void arg) { return 1; - } + } } diff --git a/src/cd/backend/codegen/StmtGenerator.java b/src/cd/backend/codegen/StmtGenerator.java index f8797d1..0d294aa 100644 --- a/src/cd/backend/codegen/StmtGenerator.java +++ b/src/cd/backend/codegen/StmtGenerator.java @@ -1,34 +1,31 @@ package cd.backend.codegen; import cd.Config; -import cd.backend.ExitCode; -import cd.backend.codegen.AstCodeGenerator.Label; import cd.backend.codegen.RegisterManager.Register; -import cd.frontend.semantic.ReturnCheckerVisitor; import cd.ir.Ast; import cd.ir.Ast.*; import cd.ir.AstVisitor; -import cd.ir.Symbol; -import cd.ir.Symbol.ClassSymbol; +import cd.ir.CompileTimeEvaluator; +import cd.ir.ExprVisitor; import cd.ir.Symbol.MethodSymbol; +import cd.ir.Symbol.PrimitiveTypeSymbol; +import cd.util.Pair; import cd.util.debug.AstOneLine; -import java.util.*; +import java.util.List; -import static cd.backend.codegen.AstCodeGenerator.VAR_SIZE; -import static cd.backend.codegen.AstCodeGenerator.TRUE; +import static cd.backend.codegen.AssemblyEmitter.arrayAddress; import static cd.backend.codegen.RegisterManager.BASE_REG; import static cd.backend.codegen.RegisterManager.STACK_REG; /** * Generates code to process statements and declarations. */ -class StmtGenerator extends AstVisitor { +class StmtGenerator extends AstVisitor { protected final AstCodeGenerator cg; StmtGenerator(AstCodeGenerator astCodeGenerator) { cg = astCodeGenerator; - } public void gen(Ast ast) { @@ -36,253 +33,337 @@ class StmtGenerator extends AstVisitor { } @Override - public Register visit(Ast ast, Location arg) { + public Register visit(Ast ast, Void arg) { try { cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast)); - if (cg.rnv.calc(ast) > cg.rm.availableRegisters()) { - Deque pushed = new ArrayDeque<>(); - for (Register r : RegisterManager.GPR) { - if (cg.rm.isInUse(r)) { - cg.emit.emit("push", r); - pushed.push(r); - cg.rm.releaseRegister(r); - } - } - Register result = super.visit(ast, arg); - for (Register r : pushed) - cg.rm.useRegister(r); - Register finalResult = cg.rm.getRegister(); - cg.emit.emitMove(result, finalResult); - for (Register r = pushed.pop(); !pushed.isEmpty(); r = pushed.pop()) - cg.emit.emit("pop", r); - return finalResult; - } else return super.visit(ast, arg); + return super.visit(ast, arg); } finally { cg.emit.decreaseIndent(); } } - @Override - public Register methodCall(MethodCall ast, Location arg) { - Register result = cg.eg.visit(ast.getMethodCallExpr(), arg); - if (result != null) - cg.rm.releaseRegister(result); - return null; - } public Register methodCall(MethodSymbol sym, List allArguments) { throw new RuntimeException("Not required"); } - /** - * vtable structure for a class type: pointer to superclass, and methods, each with 2 entries, name's hashCode and - * pointer to method execution - */ +} + +/* + * StmtGenerator with the reference solution + */ +class StmtGeneratorRef extends StmtGenerator { + + /* cg and cgRef are the same instance. cgRef simply + * provides a wider interface */ + protected final AstCodeGeneratorRef cgRef; + + StmtGeneratorRef(AstCodeGeneratorRef astCodeGenerator) { + super(astCodeGenerator); + this.cgRef = astCodeGenerator; + } + @Override - public Register classDecl(ClassDecl ast, Location arg) { - cg.emit.emitRaw(Config.DATA_INT_SECTION); - // Emit vtable for class - cg.emit.emitLabel(Label.type(ast.sym)); // Label - cg.emit.emitConstantData(Label.type(ast.sym.superClass)); // Superclass - // Methods (don't write those that are overridden twice) - Set generated = new HashSet<>(); - ClassSymbol currSym = ast.sym; - while (currSym != ClassSymbol.objectType) { - ClassSymbol finalCurrSym = currSym; - currSym.methods.values().forEach(o -> { - if (!generated.add(o.name)) return; - cg.emit.emitConstantData(String.valueOf(o.name.hashCode())); - cg.emit.emitConstantData(Label.method(finalCurrSym, o)); - }); - currSym = currSym.superClass; + public Register methodCall(MethodSymbol mthSymbol, List allArgs) { + // Push the arguments and the method prefix (caller save register, + // and padding) onto the stack. + // Note that the space for the arguments is not already reserved, + // so we just push them in the Java left-to-right order. + // + // After each iteration of the following loop, reg holds the + // register used for the previous argument. + int padding = cgRef.emitCallPrefix(null, allArgs.size()); + + Register reg = null; + for (int i = 0; i < allArgs.size(); i++) { + if (reg != null) { + cgRef.rm.releaseRegister(reg); + } + reg = cgRef.eg.gen(allArgs.get(i)); + cgRef.push(reg.repr); } - // End of class vtable - // Array vtable - boolean found = false; - for (Symbol.TypeSymbol type : cg.main.allTypeSymbols) { - if (type instanceof Symbol.ArrayTypeSymbol) { - Symbol.ArrayTypeSymbol aType = (Symbol.ArrayTypeSymbol) type; - if (aType.elementType == ast.sym) { - cg.emit.emitLabel(Label.type(aType)); // Label - found = true; - break; - } + + // Since "this" is the first parameter that push + // we have to get it back to resolve the method call + cgRef.emit.emitComment("Load \"this\" pointer"); + cgRef.emit.emitLoad((allArgs.size() - 1) * Config.SIZEOF_PTR, STACK_REG, reg); + + // Check for a null receiver + int cnPadding = cgRef.emitCallPrefix(null, 1); + cgRef.push(reg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL); + cgRef.emitCallSuffix(null, 1, cnPadding); + + // Load the address of the method to call into "reg" + // and call it indirectly. + cgRef.emit.emitLoad(0, reg, reg); + int mthdoffset = 4 + mthSymbol.vtableIndex * Config.SIZEOF_PTR; + cgRef.emit.emitLoad(mthdoffset, reg, reg); + cgRef.emit.emit("call", "*" + reg); + + cgRef.emitCallSuffix(reg, allArgs.size(), padding); + + if (mthSymbol.returnType == PrimitiveTypeSymbol.voidType) { + cgRef.rm.releaseRegister(reg); + return null; + } + return reg; + } + + @Override + public Register methodCall(MethodCall ast, Void dummy) { + Register reg = cgRef.eg.gen(ast.getMethodCallExpr()); + if (reg != null) + cgRef.rm.releaseRegister(reg); + + return reg; + } + + @Override + public Register classDecl(ClassDecl ast, Void arg) { + // Emit each method: + cgRef.emit.emitCommentSection("Class " + ast.name); + return visitChildren(ast, arg); + } + + @Override + public Register methodDecl(MethodDecl ast, Void arg) { + cgRef.emitMethodPrefix(ast); + gen(ast.body()); + cgRef.emitMethodSuffix(false); + return null; + } + + @Override + public Register ifElse(IfElse ast, Void arg) { + String falseLbl = cgRef.emit.uniqueLabel(); + String doneLbl = cgRef.emit.uniqueLabel(); + + cgRef.genJumpIfFalse(ast.condition(), falseLbl); + gen(ast.then()); + cgRef.emit.emit("jmp", doneLbl); + cgRef.emit.emitLabel(falseLbl); + gen(ast.otherwise()); + cgRef.emit.emitLabel(doneLbl); + + return null; + } + + @Override + public Register whileLoop(WhileLoop ast, Void arg) { + String nextLbl = cgRef.emit.uniqueLabel(); + String doneLbl = cgRef.emit.uniqueLabel(); + + cgRef.emit.emitLabel(nextLbl); + cgRef.genJumpIfFalse(ast.condition(), doneLbl); + gen(ast.body()); + cgRef.emit.emit("jmp", nextLbl); + cgRef.emit.emitLabel(doneLbl); + + return null; + } + + @Override + public Register assign(Assign ast, Void arg) { + class AssignVisitor extends ExprVisitor { + + @Override + public Void var(Var ast, Expr right) { + final Register rhsReg = cgRef.eg.gen(right); + cgRef.emit.emitStore(rhsReg, ast.sym.offset, BASE_REG); + cgRef.rm.releaseRegister(rhsReg); + return null; + } + + @Override + public Void field(Field ast, Expr right) { + final Register rhsReg = cgRef.eg.gen(right); + Pair regs = cgRef.egRef.genPushing(rhsReg, ast.arg()); + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(regs.b.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL); + cgRef.emitCallSuffix(null, 1, padding); + + cgRef.emit.emitStore(regs.a, ast.sym.offset, regs.b); + cgRef.rm.releaseRegister(regs.b); + cgRef.rm.releaseRegister(regs.a); + + return null; + } + + @Override + public Void index(Index ast, Expr right) { + Register rhsReg = cgRef.egRef.gen(right); + + Pair regs = cgRef.egRef.genPushing(rhsReg, ast.left()); + rhsReg = regs.a; + Register arrReg = regs.b; + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(arrReg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL); + cgRef.emitCallSuffix(null, 1, padding); + + regs = cgRef.egRef.genPushing(arrReg, ast.right()); + arrReg = regs.a; + Register idxReg = regs.b; + + // Check array bounds + padding = cgRef.emitCallPrefix(null, 2); + cgRef.push(idxReg.repr); + cgRef.push(arrReg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_BOUNDS); + cgRef.emitCallSuffix(null, 2, padding); + + cgRef.emit.emitMove(rhsReg, arrayAddress(arrReg, idxReg)); + cgRef.rm.releaseRegister(arrReg); + cgRef.rm.releaseRegister(idxReg); + cgRef.rm.releaseRegister(rhsReg); + + return null; + } + + @Override + protected Void dfltExpr(Expr ast, Expr arg) { + throw new RuntimeException("Store to unexpected lvalue " + ast); + } + + } + new AssignVisitor().visit(ast.left(), ast.right()); + + return null; + } + + @Override + public Register builtInWrite(BuiltInWrite ast, Void arg) { + Register reg = cgRef.eg.gen(ast.arg()); + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(reg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.PRINT_INTEGER); + cgRef.emitCallSuffix(null, 1, padding); + cgRef.rm.releaseRegister(reg); + + return null; + } + + @Override + public Register builtInWriteln(BuiltInWriteln ast, Void arg) { + int padding = cgRef.emitCallPrefix(null, 0); + cgRef.emit.emit("call", AstCodeGeneratorRef.PRINT_NEW_LINE); + cgRef.emitCallSuffix(null, 0, padding); + return null; + } + + @Override + public Register returnStmt(ReturnStmt ast, Void arg) { + if (ast.arg() != null) { + Register reg = cgRef.eg.gen(ast.arg()); + cgRef.emit.emitMove(reg, "%eax"); + cgRef.emitMethodSuffix(false); + cgRef.rm.releaseRegister(reg); + } else { + cgRef.emitMethodSuffix(true); // no return value -- return NULL as + // a default (required for main()) + } + + return null; + } + +} + +class StmtGeneratorNop90 extends StmtGeneratorRef { + protected final CfgCodeGenerator cfgCg; + protected CompileTimeEvaluator cte; + + StmtGeneratorNop90(AstCodeGeneratorNop90 astCodeGenerator, CfgCodeGenerator cfgCodeGenerator) { + super(astCodeGenerator); + this.cfgCg = cfgCodeGenerator; + cte = new CompileTimeEvaluator(); + } + + + @Override + public Register assign(Assign ast, Void arg) { + /* + if (ast.left() instanceof Ast.Var) { + Ast.Var var = (Ast.Var) ast.left(); + VariableSymbol sym = var.sym; + MaybeC> maybeStateList = cfgCg.unusedAssignmentsState.get(sym); + List stateList = maybeStateList.getValue(); + if (stateList.contains(ast)) { + return null; } } - if (!found) - throw new RuntimeException("The array type could not be found"); - cg.emit.emitConstantData(Label.type(ClassSymbol.objectType)); // Supertype - cg.emit.emitConstantData(Label.type(ast.sym)); // Type of elements - // End of array vtable - cg.emit.emitRaw(Config.TEXT_SECTION); - - // Method bodies - for (Ast method : ast.methods()) - visit(method, new Location(ast.sym)); - - return null; - } + */ + class AssignVisitor extends ExprVisitor { - @Override - public Register methodDecl(MethodDecl ast, Location arg) { - // Bookkeeping for framework - arg.enterMethod(ast.sym); - cg.initMethodData(); + @Override + public Void var(Var ast, Expr right) { + final Register rhsReg = cgRef.eg.gen(right); + cgRef.emit.emitStore(rhsReg, ast.sym.offset, BASE_REG); + cgRef.rm.releaseRegister(rhsReg); + return null; + } - // Begin method - cg.emit.emitLabel(Label.method(arg.classSym(), ast.sym)); + @Override + public Void field(Field ast, Expr right) { + final Register rhsReg = cgRef.eg.gen(right); + Pair regs = cgRef.egRef.genPushing(rhsReg, ast.arg()); + if (!cfgCg.check.isNotNull(ast.arg())) { + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(regs.b.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL); + cgRef.emitCallSuffix(null, 1, padding); + } + cgRef.emit.emitStore(regs.a, ast.sym.offset, regs.b); + cgRef.rm.releaseRegister(regs.b); + cgRef.rm.releaseRegister(regs.a); + return null; + } - // 1. Save and update the base register, reserve space for local variables - cg.emit.emit("enter", ast.sym.locals.size() * VAR_SIZE, 0); - for (int i = 0; i < ast.sym.locals.size(); i++) - cg.emit.emitStore(0, -(i + 1) * VAR_SIZE, BASE_REG); + @Override + public Void index(Index ast, Expr right) { + boolean isNotNull = cfgCg.check.isNotNull(ast.left()); + boolean emitBoundCheck = cfgCg.check.checkArrayBound(ast); + Register rhsReg = cgRef.egRef.gen(right); - // 2. Save CPU registers - Register[] regs = RegisterManager.CALLEE_SAVE; - for (int i = 0; i < regs.length; i++) - cg.emit.emit("push", regs[i]); + Pair regs = cgRef.egRef.genPushing(rhsReg, ast.left()); + rhsReg = regs.a; + Register arrReg = regs.b; + if (!isNotNull) { + int padding = cgRef.emitCallPrefix(null, 1); + cgRef.push(arrReg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL); + cgRef.emitCallSuffix(null, 1, padding); + } - // 3. Run the code contained in the function - Register returnReg = visit(ast.body(), arg); - cg.emit.emitLabel(Label.returnMethod(arg.classSym(), ast.sym)); + regs = cgRef.egRef.genPushing(arrReg, ast.right()); + arrReg = regs.a; + Register idxReg = regs.b; - // 4. Restore saved registers (if there is a mismatch, the stack will be corrupted) - for (int i = regs.length - 1; i >= 0; i--) - cg.emit.emit("pop", regs[i]); + // Check array bounds + if (emitBoundCheck) { + int padding = cgRef.emitCallPrefix(null, 2); + cgRef.push(idxReg.repr); + cgRef.push(arrReg.repr); + cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_BOUNDS); + cgRef.emitCallSuffix(null, 2, padding); + } - // 5 and 6: Restore the old base pointer and return from the function - cg.emitMethodSuffix(returnReg == null); + cgRef.emit.emitMove(rhsReg, arrayAddress(arrReg, idxReg)); + cgRef.rm.releaseRegister(arrReg); + cgRef.rm.releaseRegister(idxReg); + cgRef.rm.releaseRegister(rhsReg); - // Framework bookkeeping - arg.leaveMethod(); - return null; - } + return null; + } + + @Override + protected Void dfltExpr(Expr ast, Expr arg) { + throw new RuntimeException("Store to unexpected lvalue " + ast); + } - @Override - public Register ifElse(IfElse ast, Location arg) { - // Emit condition check - Register conditionRegister = cg.eg.visit(ast.condition(), arg); - // If both blocks are empty, no more code need be generated, and the condition can be ignored - if (ast.then().children().isEmpty() && ast.otherwise().children().isEmpty()) { - // Generate the condition and ignore the result - cg.rm.releaseRegister(conditionRegister); - return null; - } - String notIfLabel = cg.emit.uniqueLabel(); - String endLabel = cg.emit.uniqueLabel(); - - cg.emit.emit("cmp", TRUE, conditionRegister); - cg.rm.releaseRegister(conditionRegister); - if (!ast.then().children().isEmpty()) { - cg.emit.emit("jne", notIfLabel); - visit(ast.then(), arg); - // If there is no otherwise, the jump instruction makes no sense - // as the next code executed will be the next instruction - if (!ast.otherwise().children().isEmpty()) - cg.emit.emit("jmp", endLabel); - } else { - // No if, therefore the else follows the condition immediately - cg.emit.emit("je", endLabel); - } - - cg.emit.emitLabel(notIfLabel); - // Emit otherwise - visit(ast.otherwise(), arg); - cg.emit.emitLabel(endLabel); - - // Check if the ifElse ast node contains a return statement - ReturnCheckerVisitor rc = new ReturnCheckerVisitor(); - if (rc.ifElse(ast, null)) { - return Register.EAX; - } else { - return null; - } - } - - @Override - public Register whileLoop(WhileLoop ast, Location arg) { - String conditionLabel = cg.emit.uniqueLabel(); - - cg.emit.emitLabel(conditionLabel); - Register conditionRegister = cg.eg.visit(ast.condition(), arg); - cg.emit.emit("cmp", TRUE, conditionRegister); - cg.rm.releaseRegister(conditionRegister); - if (ast.body().children().isEmpty()) { - // Jump structure can be easier if there is no body - cg.emit.emit("je", conditionLabel); - } else { - String endLabel = cg.emit.uniqueLabel(); - // Emit jumps, labels and body - cg.emit.emit("jne", endLabel); - visit(ast.body(), arg); - cg.emit.emit("jmp", conditionLabel); - cg.emit.emitLabel(endLabel); - } - return null; - } - - @Override - public Register assign(Assign ast, Location arg) { - Register value = cg.eg.visit(ast.right(), arg); - - // If the type is a reference, visiting the lhs will yield its address - // else, the lhs will yield the current value. We need a visitor that - // returns a pointer to the position of the variable/field in memory - // for primitive types. - Register pointer = cg.eg.visit(ast.left(), arg.obtainReference()); - if (ast.left().type.isReferenceType()) { - // Check null pointer - String validPointerLabel = cg.emit.uniqueLabel(); - cg.emit.emit("cmp", 0, pointer); - cg.emit.emit("jne", validPointerLabel); - Interrupts.exit(cg, ExitCode.NULL_POINTER); - cg.emit.emitLabel(validPointerLabel); - } - cg.emit.emitStore(value, 0, pointer); - cg.rm.releaseRegister(pointer); - cg.rm.releaseRegister(value); - return null; - } - - @Override - public Register builtInWrite(BuiltInWrite ast, Location arg) { - Register reg = cg.eg.visit(ast.arg(), arg); - cg.emit.emit("sub", 16, STACK_REG); - cg.emit.emitStore(reg, 4, STACK_REG); - cg.emit.emitStore("$STR_D", 0, STACK_REG); - cg.emit.emit("call", Config.PRINTF); - cg.emit.emit("add", 16, STACK_REG); - cg.rm.releaseRegister(reg); - return null; - } - - @Override - public Register builtInWriteln(BuiltInWriteln ast, Location arg) { - cg.emit.emit("sub", 16, STACK_REG); - cg.emit.emitStore("$STR_NL", 0, STACK_REG); - cg.emit.emit("call", Config.PRINTF); - cg.emit.emit("add", 16, STACK_REG); - return null; - } - - @Override - public Register returnStmt(ReturnStmt ast, Location arg) { - if (ast.arg() != null) { - Register retReg = cg.eg.visit(ast.arg(), arg); - cg.emit.emitMove(retReg, Register.EAX); - cg.rm.releaseRegister(retReg); - } - cg.emit.emit("jmp", Label.returnMethod(arg.classSym(), arg.methodSym())); - return Register.EAX; - } - - @Override - public Register seq(Seq ast, Location arg) { - for (Ast instruction : ast.children()) { - Register res = visit(instruction, arg); - if (res != null) - return res; // don't generate instructions after a return } + new AssignVisitor().visit(ast.left(), ast.right()); return null; } } diff --git a/src/cd/frontend/semantic/FieldQualifier.java b/src/cd/frontend/semantic/FieldQualifier.java new file mode 100644 index 0000000..cc2e7ec --- /dev/null +++ b/src/cd/frontend/semantic/FieldQualifier.java @@ -0,0 +1,40 @@ +package cd.frontend.semantic; + +import cd.ir.Ast; +import cd.ir.Ast.ClassDecl; +import cd.ir.Ast.Var; +import cd.ir.AstRewriteVisitor; +import cd.ir.Symbol.ClassSymbol; + +/** + * Runs after the semantic check and rewrites expressions to be more normalized. + * References to a field {@code foo} are rewritten to always use + * {@link Ast.Field} objects (i.e., {@code this.foo}. + */ +public class FieldQualifier { + + public void rewrite(ClassDecl cd) { + AstRewriteVisitor rewriter = new AstRewriteVisitor() { + @Override + public Ast var(Var ast, ClassSymbol cs) { + switch (ast.sym.kind) { + case PARAM: + case LOCAL: + // Leave params or local variables alone + return ast; + case FIELD: + // Convert an implicit field reference to "this.foo" + Ast.Field f = new Ast.Field(new Ast.ThisRef(), ast.name); + f.arg().type = cs; + f.sym = ast.sym; + f.type = ast.type; + return f; + } + throw new RuntimeException("Unknown kind of var"); + } + }; + + cd.accept(rewriter, cd.sym); + } + +} \ No newline at end of file diff --git a/src/cd/frontend/semantic/InheritanceChecker.java b/src/cd/frontend/semantic/InheritanceChecker.java index e41b466..5ec2d5b 100644 --- a/src/cd/frontend/semantic/InheritanceChecker.java +++ b/src/cd/frontend/semantic/InheritanceChecker.java @@ -13,13 +13,13 @@ import java.util.HashSet; import java.util.Set; public class InheritanceChecker extends AstVisitor { - + ClassSymbol classSym; @Override public Void classDecl(ClassDecl ast, Void arg) { classSym = ast.sym; - + // check for cycles in the inheritance hierarchy: Set supers = new HashSet(); ClassSymbol sc = classSym.superClass; @@ -28,30 +28,31 @@ public class InheritanceChecker extends AstVisitor { if (supers.contains(sc)) throw new SemanticFailure( Cause.CIRCULAR_INHERITANCE, - "Class %s has %s as a superclass twice", + "Class %s has %s as a superclass twice", ast.name, sc.name); supers.add(sc); sc = sc.superClass; } - + this.visitChildren(ast, null); - + return null; } @Override public Void methodDecl(MethodDecl ast, Void arg) { - + // check that methods overridden from a parent class agree // on number/type of parameters MethodSymbol sym = ast.sym; MethodSymbol superSym = classSym.superClass.getMethod(ast.name); + sym.overrides = superSym; if (superSym != null) { if (superSym.parameters.size() != sym.parameters.size()) throw new SemanticFailure( Cause.INVALID_OVERRIDE, "Overridden method %s has %d parameters, " + - "but original has %d", + "but original has %d", ast.name, sym.parameters.size(), superSym.parameters.size()); for (Pair pair : Pair.zip(sym.parameters, superSym.parameters)) @@ -59,17 +60,17 @@ public class InheritanceChecker extends AstVisitor { throw new SemanticFailure( Cause.INVALID_OVERRIDE, "Method parameter %s has type %s, but " + - "corresponding base class parameter %s has type %s", + "corresponding base class parameter %s has type %s", pair.a.name, pair.a.type, pair.b.name, pair.b.type); if (superSym.returnType != sym.returnType) throw new SemanticFailure( Cause.INVALID_OVERRIDE, "Overridden method %s has return type %s," + - "but its superclass has %s", + "but its superclass has %s", ast.name, sym.returnType, superSym.returnType); } - + return null; - } + } } diff --git a/src/cd/frontend/semantic/ReturnCheckerVisitor.java b/src/cd/frontend/semantic/ReturnCheckerVisitor.java index 99f4fd7..ef1ef45 100644 --- a/src/cd/frontend/semantic/ReturnCheckerVisitor.java +++ b/src/cd/frontend/semantic/ReturnCheckerVisitor.java @@ -5,22 +5,21 @@ import cd.ir.Ast.*; import cd.ir.AstVisitor; - /** * Visitor that checks if all paths of a given sequence have a * return statement. - * + *

* This visitor only needs to be used if are not using the Control Flow Graph. - * + * * @author Leo Buttiker */ -public class ReturnCheckerVisitor extends AstVisitor { +public class ReturnCheckerVisitor extends AstVisitor { @Override protected Boolean dfltStmt(Stmt ast, Void arg) { return false; } - + @Override public Boolean returnStmt(ReturnStmt ast, Void arg) { return true; @@ -28,7 +27,7 @@ public class ReturnCheckerVisitor extends AstVisitor { @Override public Boolean ifElse(IfElse ast, Void arg) { - boolean allPathHaveAReturnStmt = true; + boolean allPathHaveAReturnStmt = true; allPathHaveAReturnStmt &= visit(ast.then(), null); allPathHaveAReturnStmt &= visit(ast.otherwise(), null); return allPathHaveAReturnStmt; @@ -36,17 +35,17 @@ public class ReturnCheckerVisitor extends AstVisitor { @Override public Boolean seq(Seq ast, Void arg) { - - boolean allPathHaveAReturnStmt = false; + + boolean allPathHaveAReturnStmt = false; for (Ast child : ast.children()) { allPathHaveAReturnStmt |= this.visit(child, null); } return allPathHaveAReturnStmt; } - + @Override public Boolean whileLoop(WhileLoop ast, Void arg) { - return false; + return false; } - + } \ No newline at end of file diff --git a/src/cd/frontend/semantic/SemanticAnalyzer.java b/src/cd/frontend/semantic/SemanticAnalyzer.java index 79aff9d..2562770 100644 --- a/src/cd/frontend/semantic/SemanticAnalyzer.java +++ b/src/cd/frontend/semantic/SemanticAnalyzer.java @@ -11,70 +11,76 @@ import java.util.ArrayList; import java.util.List; public class SemanticAnalyzer { - + public final Main main; - + public SemanticAnalyzer(Main main) { this.main = main; } - - public void check(List classDecls) - throws SemanticFailure { + + public void check(List classDecls) + throws SemanticFailure { { SymTable typeSymbols = createSymbols(classDecls); checkInheritance(classDecls); checkStartPoint(typeSymbols); checkMethodBodies(typeSymbols, classDecls); + { + rewriteMethodBodies(classDecls); + main.allTypeSymbols = typeSymbols.allSymbols(); + } } } /** - * Creates a symbol table with symbols for all built-in types, + * Creates a symbol table with symbols for all built-in types, * as well as all classes and their fields and methods. Also - * creates a corresponding array symbol for every type + * creates a corresponding array symbol for every type * (named {@code type[]}). + * * @see SymbolCreator */ private SymTable createSymbols(List classDecls) { - + // Start by creating a symbol for all built-in types. SymTable typeSymbols = new SymTable(null); - + typeSymbols.add(PrimitiveTypeSymbol.intType); typeSymbols.add(PrimitiveTypeSymbol.booleanType); typeSymbols.add(PrimitiveTypeSymbol.voidType); typeSymbols.add(ClassSymbol.objectType); - + // Add symbols for all declared classes. for (ClassDecl ast : classDecls) { // Check for classes named Object if (ast.name.equals(ClassSymbol.objectType.name)) throw new SemanticFailure(Cause.OBJECT_CLASS_DEFINED); ast.sym = new ClassSymbol(ast); - typeSymbols.add(ast.sym); + typeSymbols.add(ast.sym); } - + // Create symbols for arrays of each type. for (Symbol sym : new ArrayList(typeSymbols.localSymbols())) { - Symbol.ArrayTypeSymbol array = - new Symbol.ArrayTypeSymbol((TypeSymbol) sym); + Symbol.ArrayTypeSymbol array = + new Symbol.ArrayTypeSymbol((TypeSymbol) sym); typeSymbols.add(array); } - + // For each class, create symbols for each method and field SymbolCreator sc = new SymbolCreator(main, typeSymbols); for (ClassDecl ast : classDecls) sc.createSymbols(ast); - + return typeSymbols; } - + /** - * Check for errors related to inheritance: + * Check for errors related to inheritance: * circular inheritance, invalid super * classes, methods with different types, etc. * Note that this must be run early because other code assumes * that the inheritance is correct, for type checking etc. + * * @see InheritanceChecker */ private void checkInheritance(List classDecls) { @@ -93,25 +99,26 @@ public class SemanticAnalyzer { MethodSymbol mainMethod = cs.getMethod("main"); if (mainMethod != null && mainMethod.parameters.size() == 0 && mainMethod.returnType == PrimitiveTypeSymbol.voidType) { + main.mainType = cs; return; // found the main() method! } } throw new SemanticFailure(Cause.INVALID_START_POINT, "No Main class with method 'void main()' found"); } - + /** * Check the bodies of methods for errors, particularly type errors * but also undefined identifiers and the like. + * * @see TypeChecker */ private void checkMethodBodies( SymTable typeSymbols, - List classDecls) - { + List classDecls) { TypeChecker tc = new TypeChecker(typeSymbols); - + for (ClassDecl classd : classDecls) { - + SymTable fldTable = new SymTable(null); // add all fields of this class, or any of its super classes @@ -119,37 +126,42 @@ public class SemanticAnalyzer { for (VariableSymbol s : p.fields.values()) if (!fldTable.contains(s.name)) fldTable.add(s); - + // type check any method bodies and final locals for (MethodDecl md : classd.methods()) { - + boolean hasReturn = new ReturnCheckerVisitor().visit(md.body(), null); - + if (!md.returnType.equals("void") && !hasReturn) { - - throw new SemanticFailure(Cause.MISSING_RETURN, - "Method %s.%s is missing a return statement", - classd.name, + + throw new SemanticFailure(Cause.MISSING_RETURN, + "Method %s.%s is missing a return statement", + classd.name, md.name); - + } - + SymTable mthdTable = new SymTable(fldTable); - + mthdTable.add(classd.sym.thisSymbol); - + for (VariableSymbol p : md.sym.parameters) { mthdTable.add(p); } - + for (VariableSymbol l : md.sym.locals.values()) { mthdTable.add(l); } - + tc.checkMethodDecl(md, mthdTable); - + } } } + private void rewriteMethodBodies(List classDecls) { + for (ClassDecl cd : classDecls) + new FieldQualifier().rewrite(cd); + } + } diff --git a/src/cd/frontend/semantic/SemanticFailure.java b/src/cd/frontend/semantic/SemanticFailure.java index 02464de..8ffc8fd 100644 --- a/src/cd/frontend/semantic/SemanticFailure.java +++ b/src/cd/frontend/semantic/SemanticFailure.java @@ -1,40 +1,50 @@ package cd.frontend.semantic; -/** +/** * Thrown by the semantic checker when a semantic error is detected - * in the user's program. */ + * in the user's program. + */ public class SemanticFailure extends RuntimeException { private static final long serialVersionUID = 5375946759285719123L; - + public enum Cause { - - /** + + /** * Caused by an assignment to either a final field, {@code this}, * or some other kind of expression which cannot be assigned to. * Not used for type errors in assignments, which fall - * under {@link #TYPE_ERROR}. */ + * under {@link #TYPE_ERROR}. + */ NOT_ASSIGNABLE, - /** Two variables, fields, methods, or classes with the same name - * were declared in the same scope */ + /** + * Two variables, fields, methods, or classes with the same name + * were declared in the same scope + */ DOUBLE_DECLARATION, - /** A field was accessed that does not exist */ + /** + * A field was accessed that does not exist + */ NO_SUCH_FIELD, - - /** A method was called that does not exist */ + + /** + * A method was called that does not exist + */ NO_SUCH_METHOD, - - /** + + /** * A variable or other identifier was used in a method - * body which has no corresponding declaration */ + * body which has no corresponding declaration + */ NO_SUCH_VARIABLE, - - /** - * A method with a return type is missing a return statement among one of its paths */ + + /** + * A method with a return type is missing a return statement among one of its paths + */ MISSING_RETURN, - - /** + + /** * Can occur in many contents: *

    *
  • Assignment to a variable from an expression of wrong type @@ -53,12 +63,12 @@ public class SemanticFailure extends RuntimeException { *
*/ TYPE_ERROR, - + /** * A class is its own super class */ CIRCULAR_INHERITANCE, - + /** * One of the following: *
    @@ -69,13 +79,13 @@ public class SemanticFailure extends RuntimeException { *
*/ INVALID_START_POINT, - + /** * A class {@code Object} was defined. This class is implicitly * defined and cannot be defined explicitly. */ OBJECT_CLASS_DEFINED, - + /** * A type name was found for which no class declaration exists. * This can occur in many contexts: @@ -86,34 +96,36 @@ public class SemanticFailure extends RuntimeException { * */ NO_SUCH_TYPE, - + /** * The parameters of an overridden method have different types - * from the base method, there is a different + * from the base method, there is a different * number of parameters, or the return value is different. */ INVALID_OVERRIDE, - - /** A method was called with the wrong number of arguments */ + + /** + * A method was called with the wrong number of arguments + */ WRONG_NUMBER_OF_ARGUMENTS, - - /** + + /** * Indicates the use of a local variable that may not have been * initialized (ACD only). */ POSSIBLY_UNINITIALIZED, } - + public final Cause cause; - + public SemanticFailure(Cause cause) { super(cause.name()); this.cause = cause; } - + public SemanticFailure(Cause cause, String format, Object... args) { super(String.format(format, args)); this.cause = cause; } - + } diff --git a/src/cd/frontend/semantic/SymTable.java b/src/cd/frontend/semantic/SymTable.java index b1815aa..59ef455 100644 --- a/src/cd/frontend/semantic/SymTable.java +++ b/src/cd/frontend/semantic/SymTable.java @@ -3,24 +3,23 @@ package cd.frontend.semantic; import cd.frontend.semantic.SemanticFailure.Cause; import cd.ir.Symbol; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; -/** - * A simple symbol table, with a pointer to the enclosing scope. +/** + * A simple symbol table, with a pointer to the enclosing scope. * Used by {@link TypeChecker} to store the various scopes for - * local, parameter, and field lookup. */ + * local, parameter, and field lookup. + */ public class SymTable { - + private final Map map = new HashMap(); - + private final SymTable parent; - + public SymTable(SymTable parent) { this.parent = parent; } - + public void add(S sym) { // check that the symbol is not already declared *at this level* if (containsLocally(sym.name)) @@ -28,10 +27,21 @@ public class SymTable { map.put(sym.name, sym); } + public List allSymbols() { + List result = new ArrayList(); + SymTable st = this; + while (st != null) { + for (S sym : st.map.values()) + result.add(sym); + st = st.parent; + } + return result; + } + public Collection localSymbols() { return this.map.values(); } - + /** * True if there is a declaration with the given name at any * level in the symbol table @@ -40,7 +50,7 @@ public class SymTable { return get(name) != null; } - /** + /** * True if there is a declaration at THIS level in the symbol * table; may return {@code false} even if a declaration exists * in some enclosing scope @@ -49,9 +59,10 @@ public class SymTable { return this.map.containsKey(name); } - /** + /** * Base method: returns {@code null} if no symbol by that - * name can be found, in this table or in its parents */ + * name can be found, in this table or in its parents + */ public S get(String name) { S res = map.get(name); if (res != null) @@ -60,11 +71,12 @@ public class SymTable { return null; return parent.get(name); } - + /** - * Finds the symbol with the given name, or fails with a + * Finds the symbol with the given name, or fails with a * NO_SUCH_TYPE error. Only really makes sense to use this - * if S == TypeSymbol, but we don't strictly forbid it... */ + * if S == TypeSymbol, but we don't strictly forbid it... + */ public S getType(String name) { S res = get(name); if (res == null) diff --git a/src/cd/frontend/semantic/SymbolCreator.java b/src/cd/frontend/semantic/SymbolCreator.java index c53916a..b8b9b21 100644 --- a/src/cd/frontend/semantic/SymbolCreator.java +++ b/src/cd/frontend/semantic/SymbolCreator.java @@ -21,10 +21,10 @@ import java.util.Set; * and local variables. */ public class SymbolCreator extends Object { - + final Main main; final SymTable typesTable; - + public SymbolCreator(Main main, SymTable typesTable) { this.main = main; this.typesTable = typesTable; @@ -34,33 +34,33 @@ public class SymbolCreator extends Object { // lookup the super class. the grammar guarantees that this // will refer to a class, if the lookup succeeds. cd.sym.superClass = (ClassSymbol) typesTable.getType(cd.superClass); - new ClassSymbolCreator(cd.sym).visitChildren(cd, null); + new ClassSymbolCreator(cd.sym).visitChildren(cd, null); } /** - * Useful method which adds a symbol to a map, checking to see + * Useful method which adds a symbol to a map, checking to see * that there is not already an entry with the same name. * If a symbol with the same name exists, throws an exception. */ - public void add(Map map, S sym) { + public void add(Map map, S sym) { if (map.containsKey(sym.name)) throw new SemanticFailure( Cause.DOUBLE_DECLARATION, - "Symbol '%s' was declared twice in the same scope", + "Symbol '%s' was declared twice in the same scope", sym.name); map.put(sym.name, sym); } - + /** * Creates symbols for all fields/constants/methods in a class. - * Uses {@link MethodSymbolCreator} to create symbols for all + * Uses {@link MethodSymbolCreator} to create symbols for all * parameters and local variables to each method as well. * Checks for duplicate members. */ public class ClassSymbolCreator extends AstVisitor { - - final ClassSymbol classSym; - + + final ClassSymbol classSym; + public ClassSymbolCreator(ClassSymbol classSym) { this.classSym = classSym; } @@ -75,9 +75,11 @@ public class SymbolCreator extends Object { @Override public Void methodDecl(MethodDecl ast, Void arg) { - + ast.sym = new MethodSymbol(ast); - + + ast.sym.owner = classSym; + add(classSym.methods, ast.sym); // create return type symbol @@ -86,13 +88,13 @@ public class SymbolCreator extends Object { } else { ast.sym.returnType = typesTable.getType(ast.returnType); } - + // create symbols for each parameter Set pnames = new HashSet(); for (int i = 0; i < ast.argumentNames.size(); i++) { String argumentName = ast.argumentNames.get(i); String argumentType = ast.argumentTypes.get(i); - if (pnames.contains(argumentName)) + if (pnames.contains(argumentName)) throw new SemanticFailure( Cause.DOUBLE_DECLARATION, "Method '%s' has two parameters named '%s'", @@ -102,23 +104,23 @@ public class SymbolCreator extends Object { argumentName, typesTable.getType(argumentType)); ast.sym.parameters.add(vs); } - + // create symbols for the local variables new MethodSymbolCreator(ast.sym).visitChildren(ast.decls(), null); return null; } - + } public class MethodSymbolCreator extends AstVisitor { - + final MethodSymbol methodSym; - + public MethodSymbolCreator(MethodSymbol methodSym) { this.methodSym = methodSym; } - + @Override public Void varDecl(VarDecl ast, Void arg) { ast.sym = new VariableSymbol( diff --git a/src/cd/frontend/semantic/TypeChecker.java b/src/cd/frontend/semantic/TypeChecker.java index 955dcf1..52bef30 100644 --- a/src/cd/frontend/semantic/TypeChecker.java +++ b/src/cd/frontend/semantic/TypeChecker.java @@ -11,7 +11,7 @@ import cd.util.debug.AstOneLine; public class TypeChecker { final private SymTable typeSymbols; - + public TypeChecker(SymTable typeSymbols) { this.typeSymbols = typeSymbols; } @@ -19,11 +19,12 @@ public class TypeChecker { public void checkMethodDecl(MethodDecl method, SymTable locals) { new MethodDeclVisitor(method, locals).visit(method.body(), null); } - + /** * Checks whether two expressions have the same type * and throws an exception otherwise. + * * @return The common type of the two expression. */ private TypeSymbol checkTypesEqual(Expr leftExpr, Expr rightExpr, SymTable locals) { @@ -32,30 +33,30 @@ public class TypeChecker { if (leftType != rightType) { throw new SemanticFailure( - Cause.TYPE_ERROR, - "Expected operand types to be equal but found %s, %s", - leftType, - rightType); + Cause.TYPE_ERROR, + "Expected operand types to be equal but found %s, %s", + leftType, + rightType); } - + return leftType; } - + private void checkTypeIsInt(TypeSymbol type) { if (type != PrimitiveTypeSymbol.intType) { throw new SemanticFailure( - Cause.TYPE_ERROR, - "Expected %s for operands but found type %s", - PrimitiveTypeSymbol.intType, - type); + Cause.TYPE_ERROR, + "Expected %s for operands but found type %s", + PrimitiveTypeSymbol.intType, + type); } } - + private ClassSymbol asClassSymbol(TypeSymbol type) { if (type instanceof ClassSymbol) return (ClassSymbol) type; throw new SemanticFailure( - Cause.TYPE_ERROR, + Cause.TYPE_ERROR, "A class type was required, but %s was found", type); } @@ -63,10 +64,10 @@ public class TypeChecker { if (type instanceof ArrayTypeSymbol) return (ArrayTypeSymbol) type; throw new SemanticFailure( - Cause.TYPE_ERROR, + Cause.TYPE_ERROR, "An array type was required, but %s was found", type); } - + private TypeSymbol typeExpr(Expr expr, SymTable locals) { return new TypingVisitor().visit(expr, locals); } @@ -80,9 +81,9 @@ public class TypeChecker { expected, actual); } - + private class MethodDeclVisitor extends AstVisitor { - + private MethodDecl method; private SymTable locals; @@ -95,7 +96,7 @@ public class TypeChecker { protected Void dfltExpr(Expr ast, Void arg) { throw new RuntimeException("Should not get here"); } - + @Override public Void assign(Assign ast, Void arg) { TypeSymbol lhs = typeLhs(ast.left(), locals); @@ -113,15 +114,15 @@ public class TypeChecker { checkType(ast.arg(), PrimitiveTypeSymbol.intType, locals); return null; } - + @Override public Void builtInWriteln(BuiltInWriteln ast, Void arg) { return null; } - + @Override public Void ifElse(IfElse ast, Void arg) { - checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals); + checkType((Expr) ast.condition(), PrimitiveTypeSymbol.booleanType, locals); visit(ast.then(), arg); if (ast.otherwise() != null) visit(ast.otherwise(), arg); @@ -131,17 +132,17 @@ public class TypeChecker { @Override public Void methodCall(MethodCall ast, Void arg) { typeExpr(ast.getMethodCallExpr(), locals); - + return null; - + } @Override public Void whileLoop(WhileLoop ast, Void arg) { - checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals); + checkType((Expr) ast.condition(), PrimitiveTypeSymbol.booleanType, locals); return visit(ast.body(), arg); } - + @Override public Void returnStmt(ReturnStmt ast, Void arg) { boolean hasArg = ast.arg() != null; @@ -150,11 +151,11 @@ public class TypeChecker { throw new SemanticFailure( Cause.TYPE_ERROR, "Return statement of method with void return type should " - + "not have arguments."); + + "not have arguments."); } else if (!hasArg) { // X m() { return; } if (method.sym.returnType != PrimitiveTypeSymbol.voidType) { - throw new SemanticFailure( + throw new SemanticFailure( Cause.TYPE_ERROR, "Return statement has no arguments. Expected %s but type was %s", method.sym.returnType, @@ -164,58 +165,58 @@ public class TypeChecker { // X m() { return y; } checkType(ast.arg(), method.sym.returnType, locals); } - + return null; } - + } private class TypingVisitor extends ExprVisitor> { - + @Override public TypeSymbol visit(Expr ast, SymTable arg) { ast.type = super.visit(ast, arg); return ast.type; } - + @Override public TypeSymbol binaryOp(BinaryOp ast, SymTable locals) { switch (ast.operator) { - case B_TIMES: - case B_DIV: - case B_MOD: - case B_PLUS: - case B_MINUS: - TypeSymbol type = checkTypesEqual(ast.left(), ast.right(), locals); - checkTypeIsInt(type); - return type; - - case B_AND: - case B_OR: - checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals); - checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals); - return PrimitiveTypeSymbol.booleanType; - - case B_EQUAL: - case B_NOT_EQUAL: - TypeSymbol left = typeExpr(ast.left(), locals); - TypeSymbol right = typeExpr(ast.right(), locals); - if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left)) - return PrimitiveTypeSymbol.booleanType; - throw new SemanticFailure( - Cause.TYPE_ERROR, - "Types %s and %s could never be equal", - left, right); - - case B_LESS_THAN: - case B_LESS_OR_EQUAL: - case B_GREATER_THAN: - case B_GREATER_OR_EQUAL: - checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals)); - return PrimitiveTypeSymbol.booleanType; - + case B_TIMES: + case B_DIV: + case B_MOD: + case B_PLUS: + case B_MINUS: + TypeSymbol type = checkTypesEqual(ast.left(), ast.right(), locals); + checkTypeIsInt(type); + return type; + + case B_AND: + case B_OR: + checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals); + checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals); + return PrimitiveTypeSymbol.booleanType; + + case B_EQUAL: + case B_NOT_EQUAL: + TypeSymbol left = typeExpr(ast.left(), locals); + TypeSymbol right = typeExpr(ast.right(), locals); + if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left)) + return PrimitiveTypeSymbol.booleanType; + throw new SemanticFailure( + Cause.TYPE_ERROR, + "Types %s and %s could never be equal", + left, right); + + case B_LESS_THAN: + case B_LESS_OR_EQUAL: + case B_GREATER_THAN: + case B_GREATER_OR_EQUAL: + checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals)); + return PrimitiveTypeSymbol.booleanType; + } - throw new RuntimeException("Unhandled operator "+ast.operator); + throw new RuntimeException("Unhandled operator " + ast.operator); } @Override @@ -232,10 +233,10 @@ public class TypeChecker { public TypeSymbol cast(Cast ast, SymTable locals) { TypeSymbol argType = typeExpr(ast.arg(), locals); ast.type = typeSymbols.getType(ast.typeName); - + if (argType.isSuperTypeOf(ast.type) || ast.type.isSuperTypeOf(argType)) return ast.type; - + throw new SemanticFailure( Cause.TYPE_ERROR, "Types %s and %s in cast are completely unrelated.", @@ -295,21 +296,20 @@ public class TypeChecker { @Override public TypeSymbol unaryOp(UnaryOp ast, SymTable locals) { - + switch (ast.operator) { - case U_PLUS: - case U_MINUS: - { - TypeSymbol type = typeExpr(ast.arg(), locals); - checkTypeIsInt(type); - return type; + case U_PLUS: + case U_MINUS: { + TypeSymbol type = typeExpr(ast.arg(), locals); + checkTypeIsInt(type); + return type; + } + + case U_BOOL_NOT: + checkType(ast.arg(), PrimitiveTypeSymbol.booleanType, locals); + return PrimitiveTypeSymbol.booleanType; } - - case U_BOOL_NOT: - checkType(ast.arg(), PrimitiveTypeSymbol.booleanType, locals); - return PrimitiveTypeSymbol.booleanType; - } - throw new RuntimeException("Unknown unary op "+ast.operator); + throw new RuntimeException("Unknown unary op " + ast.operator); } @Override @@ -322,20 +322,20 @@ public class TypeChecker { ast.setSymbol(locals.get(ast.name)); return ast.sym.type; } - + @Override public TypeSymbol methodCall(MethodCallExpr ast, SymTable locals) { - ClassSymbol rcvrType = asClassSymbol(typeExpr(ast.receiver(), locals)); + ClassSymbol rcvrType = asClassSymbol(typeExpr(ast.receiver(), locals)); MethodSymbol mthd = rcvrType.getMethod(ast.methodName); if (mthd == null) throw new SemanticFailure( Cause.NO_SUCH_METHOD, "Class %s has no method %s()", - rcvrType.name, ast.methodName); - + rcvrType.name, ast.methodName); + ast.sym = mthd; - + // Check that the number of arguments is correct. if (ast.argumentsWithoutReceiver().size() != mthd.parameters.size()) throw new SemanticFailure( @@ -344,33 +344,36 @@ public class TypeChecker { ast.methodName, mthd.parameters.size(), ast.argumentsWithoutReceiver().size()); - + // Check that the arguments are of correct type. int i = 0; for (Ast argAst : ast.argumentsWithoutReceiver()) - checkType((Expr)argAst, mthd.parameters.get(i++).type, locals); - + checkType((Expr) argAst, mthd.parameters.get(i++).type, locals); + return ast.sym.returnType; - + } } - + /** * Checks an expr as the left-hand-side of an assignment, * returning the type of value that may be assigned there. * May fail if the expression is not a valid LHS (for example, - * a "final" field). */ + * a "final" field). + */ private TypeSymbol typeLhs(Expr expr, SymTable locals) { return new LValueVisitor().visit(expr, locals); } - + /** * @see TypeChecker#typeLhs(Expr, SymTable) */ private class LValueVisitor extends ExprVisitor> { - /** Fields, array-indexing, and vars can be on the LHS: */ - + /** + * Fields, array-indexing, and vars can be on the LHS: + */ + @Override public TypeSymbol field(Field ast, SymTable locals) { return typeExpr(ast, locals); @@ -385,13 +388,15 @@ public class TypeChecker { public TypeSymbol var(Var ast, SymTable locals) { return typeExpr(ast, locals); } - - /** Any other kind of expression is not a value lvalue */ + + /** + * Any other kind of expression is not a value lvalue + */ @Override protected TypeSymbol dfltExpr(Expr ast, SymTable locals) { throw new SemanticFailure( Cause.NOT_ASSIGNABLE, - "'%s' is not a valid lvalue", + "'%s' is not a valid lvalue", AstOneLine.toString(ast)); } } diff --git a/src/cd/ir/Ast.java b/src/cd/ir/Ast.java index 7589a66..700364b 100644 --- a/src/cd/ir/Ast.java +++ b/src/cd/ir/Ast.java @@ -17,20 +17,20 @@ public abstract class Ast { /** * The list of children AST nodes. Typically, this list is of a fixed size: its contents * can also be accessed using the various accessors defined on the Ast subtypes. - * - *

Note: this list may contain null pointers! + * + *

Note: this list may contain null pointers! */ public final List rwChildren; - + protected Ast(int fixedCount) { if (fixedCount == -1) this.rwChildren = new ArrayList(); else this.rwChildren = Arrays.asList(new Ast[fixedCount]); } - - /** - * Returns a copy of the list of children for this node. The result will + + /** + * Returns a copy of the list of children for this node. The result will * never contain null pointers. */ public List children() { @@ -40,9 +40,9 @@ public abstract class Ast { } return result; } - - /** - * Returns a new list containing all children AST nodes + + /** + * Returns a new list containing all children AST nodes * that are of the given type. */ public List childrenOfType(Class C) { @@ -53,163 +53,222 @@ public abstract class Ast { } return res; } - - - /** Accept method for the pattern Visitor. */ - public abstract R accept(AstVisitor visitor, A arg); - - /** Convenient debugging printout */ + + + /** + * Accept method for the pattern Visitor. + */ + public abstract R accept(AstVisitor visitor, A arg); + + + /** + * Makes a deep clone of this AST node. + */ + public abstract Ast deepCopy(); + + /** + * Convenient debugging printout + */ @Override - public String toString() { + public String toString() { return String.format( "(%s)@%x", AstOneLine.toString(this), System.identityHashCode(this)); } - + // _________________________________________________________________ // Expressions - /** Base class for all expressions */ + /** + * Base class for all expressions + */ public static abstract class Expr extends Ast { - + protected Expr(int fixedCount) { super(fixedCount); } - - /** Type that this expression will evaluate to (computed in semantic phase). */ + + /** + * Type that this expression will evaluate to (computed in semantic phase). + */ public TypeSymbol type; - + @Override - public R accept(AstVisitor visitor, A arg) { - return this.accept((ExprVisitor)visitor, arg); + public R accept(AstVisitor visitor, A arg) { + return this.accept((ExprVisitor) visitor, arg); } - public abstract R accept(ExprVisitor visitor, A arg); - - /** Copies any non-AST fields. */ + + public abstract R accept(ExprVisitor visitor, A arg); + + /** + * Copies any non-AST fields. + */ protected E postCopy(E item) { { item.type = type; } return item; } - } - - /** Base class used for exprs with left/right operands. - * We use this for all expressions that take strictly two operands, - * such as binary operators or array indexing. */ + } + + /** + * Base class used for exprs with left/right operands. + * We use this for all expressions that take strictly two operands, + * such as binary operators or array indexing. + */ public static abstract class LeftRightExpr extends Expr { - + public LeftRightExpr(Expr left, Expr right) { super(2); assert left != null; assert right != null; - setLeft(left); + setLeft(left); setRight(right); } - - public Expr left() { return (Expr) this.rwChildren.get(0); } - public void setLeft(Expr node) { this.rwChildren.set(0, node); } - public Expr right() { return (Expr) this.rwChildren.get(1); } - public void setRight(Expr node) { this.rwChildren.set(1, node); } + public Expr left() { + return (Expr) this.rwChildren.get(0); + } + + public void setLeft(Expr node) { + this.rwChildren.set(0, node); + } + + public Expr right() { + return (Expr) this.rwChildren.get(1); + } + + public void setRight(Expr node) { + this.rwChildren.set(1, node); + } } - - /** Base class used for expressions with a single argument */ + + /** + * Base class used for expressions with a single argument + */ public static abstract class ArgExpr extends Expr { - + public ArgExpr(Expr arg) { super(1); assert arg != null; setArg(arg); } - - public Expr arg() { return (Expr) this.rwChildren.get(0); } - public void setArg(Expr node) { this.rwChildren.set(0, node); } - + + public Expr arg() { + return (Expr) this.rwChildren.get(0); + } + + public void setArg(Expr node) { + this.rwChildren.set(0, node); + } + } - - /** Base class used for things with no arguments */ - protected static abstract class LeafExpr extends Expr { + + /** + * Base class used for things with no arguments + */ + public static abstract class LeafExpr extends Expr { public LeafExpr() { super(0); } } - - /** Represents {@code this}, the current object */ + + /** + * Represents {@code this}, the current object + */ public static class ThisRef extends LeafExpr { - + @Override public R accept(ExprVisitor visitor, A arg) { return visitor.thisRef(this, arg); } - + + @Override + public ThisRef deepCopy() { + return postCopy(new ThisRef()); + } + } - - /** A binary operation combining a left and right operand, - * such as "1+2" or "3*4" */ + + /** + * A binary operation combining a left and right operand, + * such as "1+2" or "3*4" + */ public static class BinaryOp extends LeftRightExpr { - - public static enum BOp { - - B_TIMES("*"), - B_DIV("/"), - B_MOD("%"), - B_PLUS("+"), - B_MINUS("-"), - B_AND("&&"), - B_OR("||"), - B_EQUAL("=="), - B_NOT_EQUAL("!="), - B_LESS_THAN("<"), - B_LESS_OR_EQUAL("<="), - B_GREATER_THAN(">"), - B_GREATER_OR_EQUAL(">="); - - public String repr; - private BOp(String repr) { this.repr = repr; } - + + public static enum BOp { + + B_TIMES("*"), + B_DIV("/"), + B_MOD("%"), + B_PLUS("+"), + B_MINUS("-"), + B_AND("&&"), + B_OR("||"), + B_EQUAL("=="), + B_NOT_EQUAL("!="), + B_LESS_THAN("<"), + B_LESS_OR_EQUAL("<="), + B_GREATER_THAN(">"), + B_GREATER_OR_EQUAL(">="); + + public String repr; + + private BOp(String repr) { + this.repr = repr; + } + /** - * Note that this method ignores short-circuit evaluation of boolean + * Note that this method ignores short-circuit evaluation of boolean * AND/OR. - * + * * @return true iff A op B == B op A for this * operator. */ public boolean isCommutative() { - switch(this) { - case B_PLUS: - case B_TIMES: - case B_AND: - case B_OR: - case B_EQUAL: - case B_NOT_EQUAL: - return true; - default: - return false; - } + switch (this) { + case B_PLUS: + case B_TIMES: + case B_AND: + case B_OR: + case B_EQUAL: + case B_NOT_EQUAL: + return true; + default: + return false; + } } - }; - - public BOp operator; - - public BinaryOp(Expr left, BOp operator, Expr right) { - super(left, right); - this.operator = operator; - } + } + + ; + + public BOp operator; + + public BinaryOp(Expr left, BOp operator, Expr right) { + super(left, right); + this.operator = operator; + } @Override public R accept(ExprVisitor visitor, A arg) { return visitor.binaryOp(this, arg); } + @Override + public BinaryOp deepCopy() { + return postCopy(new BinaryOp(left(), operator, right())); + } + } - - /** A Cast from one type to another: {@code (typeName)arg} */ + + /** + * A Cast from one type to another: {@code (typeName)arg} + */ public static class Cast extends ArgExpr { - + public String typeName; - + public Cast(Expr arg, String typeName) { super(arg); this.typeName = typeName; @@ -219,95 +278,129 @@ public abstract class Ast { public R accept(ExprVisitor visitor, A arg) { return visitor.cast(this, arg); } - + + @Override + public Cast deepCopy() { + return postCopy(new Cast(arg(), typeName)); + } + @Override protected E postCopy(E item) { - ((Cast)item).type = type; + ((Cast) item).type = type; return super.postCopy(item); } - + } public static class IntConst extends LeafExpr { - + public final int value; + public IntConst(int value) { this.value = value; } - + @Override public R accept(ExprVisitor visitor, A arg) { return visitor.intConst(this, arg); } - + + @Override + public IntConst deepCopy() { + return postCopy(new IntConst(value)); + } + } public static class BooleanConst extends LeafExpr { - + public final boolean value; + public BooleanConst(boolean value) { this.value = value; } - + @Override public R accept(ExprVisitor visitor, A arg) { return visitor.booleanConst(this, arg); } - + + @Override + public BooleanConst deepCopy() { + return postCopy(new BooleanConst(value)); + } + } - + public static class NullConst extends LeafExpr { - + @Override public R accept(ExprVisitor visitor, A arg) { return visitor.nullConst(this, arg); } - + + @Override + public NullConst deepCopy() { + return postCopy(new NullConst()); + } + } - + public static class Field extends ArgExpr { - + public final String fieldName; - + public VariableSymbol sym; - + public Field(Expr arg, String fieldName) { super(arg); assert arg != null && fieldName != null; this.fieldName = fieldName; } - + @Override public R accept(ExprVisitor visitor, A arg) { return visitor.field(this, arg); } - + + @Override + public Field deepCopy() { + return postCopy(new Field(arg(), fieldName)); + } + @Override protected E postCopy(E item) { - ((Field)item).sym = sym; + ((Field) item).sym = sym; return super.postCopy(item); } - + } - + public static class Index extends LeftRightExpr { - + public Index(Expr array, Expr index) { - super(array, index); + super(array, index); } @Override public R accept(ExprVisitor visitor, A arg) { return visitor.index(this, arg); } - + + @Override + public Index deepCopy() { + return postCopy(new Index(left(), right())); + } + } - + public static class NewObject extends LeafExpr { - - /** Name of the type to be created */ + + /** + * Name of the type to be created + */ public String typeName; - + public NewObject(String typeName) { this.typeName = typeName; } @@ -316,14 +409,21 @@ public abstract class Ast { public R accept(ExprVisitor visitor, A arg) { return visitor.newObject(this, arg); } - + + @Override + public NewObject deepCopy() { + return postCopy(new NewObject(typeName)); + } + } - + public static class NewArray extends ArgExpr { - - /** Name of the type to be created: must be an array type */ + + /** + * Name of the type to be created: must be an array type + */ public String typeName; - + public NewArray(String typeName, Expr capacity) { super(capacity); this.typeName = typeName; @@ -333,46 +433,62 @@ public abstract class Ast { public R accept(ExprVisitor visitor, A arg) { return visitor.newArray(this, arg); } - + + @Override + public NewArray deepCopy() { + return postCopy(new NewArray(typeName, arg())); + } + } - + public static class UnaryOp extends ArgExpr { - - public static enum UOp { - U_PLUS("+"), - U_MINUS("-"), - U_BOOL_NOT("!"); - public String repr; - private UOp(String repr) { this.repr = repr; } - }; - - public final UOp operator; - - public UnaryOp(UOp operator, Expr arg) { + + public static enum UOp { + U_PLUS("+"), + U_MINUS("-"), + U_BOOL_NOT("!"); + public String repr; + + private UOp(String repr) { + this.repr = repr; + } + } + + ; + + public final UOp operator; + + public UnaryOp(UOp operator, Expr arg) { super(arg); this.operator = operator; } - + @Override public R accept(ExprVisitor visitor, A arg) { return visitor.unaryOp(this, arg); } - + + @Override + public UnaryOp deepCopy() { + return postCopy(new UnaryOp(operator, arg())); + } + } - + public static class Var extends LeafExpr { - + public String name; - + public VariableSymbol sym; - - /** + + /** * Use this constructor to build an instance of this AST - * in the parser. + * in the parser. */ public Var(String name) { this.name = name; } + @Override public R accept(ExprVisitor visitor, A arg) { return visitor.var(this, arg); @@ -389,35 +505,45 @@ public abstract class Ast { v.type = sym.type; return v; } - + + @Override + public Var deepCopy() { + return postCopy(new Var(name)); + } + @Override protected E postCopy(E item) { - ((Var)item).sym = sym; + ((Var) item).sym = sym; return super.postCopy(item); } - + public void setSymbol(VariableSymbol variableSymbol) { sym = variableSymbol; name = sym.toString(); } - + } - + public static class BuiltInRead extends LeafExpr { - + @Override public R accept(ExprVisitor visitor, A arg) { return visitor.builtInRead(this, arg); } - + + @Override + public BuiltInRead deepCopy() { + return postCopy(new BuiltInRead()); + } + } - + public static class MethodCallExpr extends Expr { - + public String methodName; - + public MethodSymbol sym; - + public MethodCallExpr(Expr rcvr, String methodName, List arguments) { super(-1); assert rcvr != null && methodName != null && arguments != null; @@ -425,72 +551,109 @@ public abstract class Ast { this.rwChildren.add(rcvr); this.rwChildren.addAll(arguments); } - - /** Returns the receiver of the method call. - * i.e., for a method call {@code a.b(c,d)} returns {@code a}. */ - public Expr receiver() { return (Expr) this.rwChildren.get(0); } - /** Changes the receiver of the method call. - * i.e., for a method call {@code a.b(c,d)} changes {@code a}. */ - public void setReceiver(Expr rcvr) { this.rwChildren.set(0, rcvr); } - - /** Returns all arguments to the method, including the receiver. - * i.e, for a method call {@code a.b(c,d)} returns {@code [a, c, d]} */ - public List allArguments() - { + /** + * Returns the receiver of the method call. + * i.e., for a method call {@code a.b(c,d)} returns {@code a}. + */ + public Expr receiver() { + return (Expr) this.rwChildren.get(0); + } + + /** + * Changes the receiver of the method call. + * i.e., for a method call {@code a.b(c,d)} changes {@code a}. + */ + public void setReceiver(Expr rcvr) { + this.rwChildren.set(0, rcvr); + } + + /** + * Returns all arguments to the method, including the receiver. + * i.e, for a method call {@code a.b(c,d)} returns {@code [a, c, d]} + */ + public List allArguments() { ArrayList result = new ArrayList(); for (Ast chi : this.rwChildren) result.add((Expr) chi); return Collections.unmodifiableList(result); } - - /** Returns all arguments to the method, without the receiver. - * i.e, for a method call {@code a.b(c,d)} returns {@code [c, d]} */ - public List argumentsWithoutReceiver() - { + + /** + * Returns all arguments to the method, without the receiver. + * i.e, for a method call {@code a.b(c,d)} returns {@code [c, d]} + */ + public List argumentsWithoutReceiver() { ArrayList result = new ArrayList(); for (int i = 1; i < this.rwChildren.size(); i++) result.add((Expr) this.rwChildren.get(i)); return Collections.unmodifiableList(result); } - + @Override public R accept(ExprVisitor visitor, A arg) { return visitor.methodCall(this, arg); } - + + public List deepCopyArguments() { + + ArrayList result = new ArrayList(); + + for (final Expr expr : argumentsWithoutReceiver()) { + result.add((Expr) expr.deepCopy()); + } + + return result; + + } + + @Override + public MethodCallExpr deepCopy() { + return postCopy(new MethodCallExpr((Expr) receiver().deepCopy(), methodName, deepCopyArguments())); + } + } - + // _________________________________________________________________ // Statements - - /** Interface for all statements */ + + /** + * Interface for all statements + */ public static abstract class Stmt extends Ast { protected Stmt(int fixedCount) { super(fixedCount); - } + } } - /** Represents an empty statement: has no effect. */ + /** + * Represents an empty statement: has no effect. + */ public static class Nop extends Stmt { - + public Nop() { super(0); } - + @Override public R accept(AstVisitor visitor, A arg) { return visitor.nop(this, arg); } - + + @Override + public Ast deepCopy() { + return new Nop(); + } + } - - /** An assignment from {@code right()} to the location - * represented by {@code left()}. + + /** + * An assignment from {@code right()} to the location + * represented by {@code left()}. */ public static class Assign extends Stmt { - + public Assign(Expr left, Expr right) { super(2); assert left != null && right != null; @@ -498,21 +661,36 @@ public abstract class Ast { setRight(right); } - public Expr left() { return (Expr) this.rwChildren.get(0); } - public void setLeft(Expr node) { this.rwChildren.set(0, node); } + public Expr left() { + return (Expr) this.rwChildren.get(0); + } - public Expr right() { return (Expr) this.rwChildren.get(1); } - public void setRight(Expr node) { this.rwChildren.set(1, node); } + public void setLeft(Expr node) { + this.rwChildren.set(0, node); + } + + public Expr right() { + return (Expr) this.rwChildren.get(1); + } + + public void setRight(Expr node) { + this.rwChildren.set(1, node); + } @Override public R accept(AstVisitor visitor, A arg) { return visitor.assign(this, arg); } - + + @Override + public Ast deepCopy() { + return new Assign((Expr) left().deepCopy(), (Expr) right().deepCopy()); + } + } - + public static class IfElse extends Stmt { - + public IfElse(Expr cond, Ast then, Ast otherwise) { super(3); assert cond != null && then != null && otherwise != null; @@ -520,131 +698,198 @@ public abstract class Ast { setThen(then); setOtherwise(otherwise); } - - public Expr condition() { return (Expr) this.rwChildren.get(0); } - public void setCondition(Expr node) { this.rwChildren.set(0, node); } - public Ast then() { return this.rwChildren.get(1); } - public void setThen(Ast node) { this.rwChildren.set(1, node); } + public Expr condition() { + return (Expr) this.rwChildren.get(0); + } - public Ast otherwise() { return this.rwChildren.get(2); } - public void setOtherwise(Ast node) { this.rwChildren.set(2, node); } + public void setCondition(Expr node) { + this.rwChildren.set(0, node); + } + + public Ast then() { + return this.rwChildren.get(1); + } + + public void setThen(Ast node) { + this.rwChildren.set(1, node); + } + + public Ast otherwise() { + return this.rwChildren.get(2); + } + + public void setOtherwise(Ast node) { + this.rwChildren.set(2, node); + } @Override public R accept(AstVisitor visitor, A arg) { return visitor.ifElse(this, arg); } - + + @Override + public Ast deepCopy() { + return new IfElse((Expr) condition().deepCopy(), then().deepCopy(), otherwise().deepCopy()); + } + } - + public static class ReturnStmt extends Stmt { - + public ReturnStmt(Expr arg) { super(1); setArg(arg); } - public Expr arg() { return (Expr) this.rwChildren.get(0); } - public void setArg(Expr node) { this.rwChildren.set(0, node); } - + public Expr arg() { + return (Expr) this.rwChildren.get(0); + } + + public void setArg(Expr node) { + this.rwChildren.set(0, node); + } + @Override public R accept(AstVisitor visitor, A arg) { return visitor.returnStmt(this, arg); } - + + @Override + public Ast deepCopy() { + return new ReturnStmt(arg() != null ? (Expr) arg().deepCopy() : null); + } + } - + public static class BuiltInWrite extends Stmt { - + public BuiltInWrite(Expr arg) { super(1); assert arg != null; setArg(arg); } - public Expr arg() { return (Expr) this.rwChildren.get(0); } - public void setArg(Expr node) { this.rwChildren.set(0, node); } - + public Expr arg() { + return (Expr) this.rwChildren.get(0); + } + + public void setArg(Expr node) { + this.rwChildren.set(0, node); + } + @Override public R accept(AstVisitor visitor, A arg) { return visitor.builtInWrite(this, arg); } - + + @Override + public Ast deepCopy() { + return new BuiltInWrite((Expr) arg().deepCopy()); + } + } - + public static class BuiltInWriteln extends Stmt { - + public BuiltInWriteln() { super(0); } - + @Override public R accept(AstVisitor visitor, A arg) { return visitor.builtInWriteln(this, arg); } - + + @Override + public Ast deepCopy() { + return new BuiltInWriteln(); + } + } - + public static class MethodCall extends Stmt { - + public MethodCall(MethodCallExpr mce) { super(1); this.rwChildren.set(0, mce); } - + public MethodCallExpr getMethodCallExpr() { - return (MethodCallExpr)this.rwChildren.get(0); + return (MethodCallExpr) this.rwChildren.get(0); } - + @Override public R accept(AstVisitor visitor, A arg) { return visitor.methodCall(this, arg); } - - } + + @Override + public Ast deepCopy() { + return new MethodCall(this.getMethodCallExpr().deepCopy()); + } + + } public static class WhileLoop extends Stmt { - + public WhileLoop(Expr condition, Ast body) { super(2); assert condition != null && body != null; setCondition(condition); setBody(body); } - - public Expr condition() { return (Expr) this.rwChildren.get(0); } - public void setCondition(Expr cond) { this.rwChildren.set(0, cond); } - - public Ast body() { return this.rwChildren.get(1); } - public void setBody(Ast body) { this.rwChildren.set(1, body); } + + public Expr condition() { + return (Expr) this.rwChildren.get(0); + } + + public void setCondition(Expr cond) { + this.rwChildren.set(0, cond); + } + + public Ast body() { + return this.rwChildren.get(1); + } + + public void setBody(Ast body) { + this.rwChildren.set(1, body); + } @Override public R accept(AstVisitor visitor, A arg) { return visitor.whileLoop(this, arg); } + + @Override + public Ast deepCopy() { + return new WhileLoop((Expr) condition().deepCopy(), body().deepCopy()); + } } - + // _________________________________________________________________ // Declarations - - /** Interface for all declarations */ + + /** + * Interface for all declarations + */ public static abstract class Decl extends Ast { protected Decl(int fixedCount) { super(fixedCount); - } + } } - + public static class VarDecl extends Decl { - + public String type; public String name; public VariableSymbol sym; - + public VarDecl(String type, String name) { this(0, type, name); } - + protected VarDecl(int num, String type, String name) { super(num); this.type = type; @@ -654,20 +899,29 @@ public abstract class Ast { @Override public R accept(AstVisitor visitor, A arg) { return visitor.varDecl(this, arg); - } - + } + + @Override + public Ast deepCopy() { + return new VarDecl(type, name); + } + } - - /** Used in {@link MethodDecl} to group together declarations - * and method bodies. */ + + /** + * Used in {@link MethodDecl} to group together declarations + * and method bodies. + */ public static class Seq extends Decl { - + public Seq(List nodes) { super(-1); if (nodes != null) this.rwChildren.addAll(nodes); } - - /** Grant access to the raw list of children for seq nodes */ + + /** + * Grant access to the raw list of children for seq nodes + */ public List rwChildren() { return this.rwChildren; } @@ -676,27 +930,43 @@ public abstract class Ast { public R accept(AstVisitor visitor, A arg) { return visitor.seq(this, arg); } + + @Override + public Ast deepCopy() { + + List result = new ArrayList(); + + for (final Ast ast : this.rwChildren) { + result.add(ast.deepCopy()); + } + + return new Seq(result); + + } } - + public static class MethodDecl extends Decl { - + public String returnType; public String name; public List argumentTypes; public List argumentNames; public MethodSymbol sym; + public ControlFlowGraph cfg; + public MethodDecl( - String returnType, + String returnType, String name, List> formalParams, Seq decls, Seq body) { this(returnType, name, Pair.unzipA(formalParams), Pair.unzipB(formalParams), decls, body); } + public MethodDecl( - String returnType, + String returnType, String name, - List argumentTypes, + List argumentTypes, List argumentNames, Seq decls, Seq body) { @@ -708,52 +978,85 @@ public abstract class Ast { setDecls(decls); setBody(body); } - - public Seq decls() { return (Seq) this.rwChildren.get(0); } - public void setDecls(Seq decls) { this.rwChildren.set(0, decls); } - - public Seq body() { return (Seq) this.rwChildren.get(1); } - public void setBody(Seq body) { this.rwChildren.set(1, body); } + + public Seq decls() { + return (Seq) this.rwChildren.get(0); + } + + public void setDecls(Seq decls) { + this.rwChildren.set(0, decls); + } + + public Seq body() { + return (Seq) this.rwChildren.get(1); + } + + public void setBody(Seq body) { + this.rwChildren.set(1, body); + } @Override public R accept(AstVisitor visitor, A arg) { return visitor.methodDecl(this, arg); } - + + @Override + public Ast deepCopy() { + return new MethodDecl( + returnType, + name, + Collections.unmodifiableList(argumentTypes), + Collections.unmodifiableList(argumentNames), + (Seq) decls().deepCopy(), + (Seq) body().deepCopy()); + } + } - + public static class ClassDecl extends Decl { - + public String name; public String superClass; public ClassSymbol sym; - + public ClassDecl( - String name, - String superClass, + String name, + String superClass, List members) { super(-1); this.name = name; this.superClass = superClass; this.rwChildren.addAll(members); } - - public List members() { - return Collections.unmodifiableList(this.rwChildren); + + public List members() { + return Collections.unmodifiableList(this.rwChildren); } - - public List fields() { - return childrenOfType(VarDecl.class); + + public List fields() { + return childrenOfType(VarDecl.class); } // includes constants! - public List methods() { - return childrenOfType(MethodDecl.class); + public List methods() { + return childrenOfType(MethodDecl.class); } @Override public R accept(AstVisitor visitor, A arg) { return visitor.classDecl(this, arg); } - + + @Override + public Ast deepCopy() { + + List result = new ArrayList(); + + for (final Ast ast : members()) { + result.add(ast.deepCopy()); + } + + return new ClassDecl(name, superClass, result); + } + } } diff --git a/src/cd/ir/AstReplaceVisitor.java b/src/cd/ir/AstReplaceVisitor.java new file mode 100755 index 0000000..06ababa --- /dev/null +++ b/src/cd/ir/AstReplaceVisitor.java @@ -0,0 +1,51 @@ +package cd.ir; + + +import cd.ir.Ast.Expr; +import cd.ir.Symbol.PrimitiveTypeSymbol; + +import java.util.Map; + +/** + * A visitor that replaces AST nodes in toBeReplaced + * used in ConstantAnalysis, deletes all the variables that have a constant value and replace them with a constant + */ + +public class AstReplaceVisitor extends AstRewriteVisitor { + + // use this information to replace each Var node with it's correct constant value + public Map toBeReplaced; + public Map initializePositions; + public int currentPosition; + + + public Ast.LeafExpr getReplacement(Ast.Var arg) { + + // Var was declared constant but initialization happens later + // replace it with IntConst or BooleanConst and their default value + if (toBeReplaced.containsKey(arg) && currentPosition <= initializePositions.get(arg)) { + //if (toBeReplaced.containsKey(arg)) { + Ast.LeafExpr leafEpr = (Ast.LeafExpr) arg; + if (leafEpr.type == PrimitiveTypeSymbol.intType) { + return new Ast.IntConst(0); + } else { + return new Ast.BooleanConst(false); + } + } + // Var was declared constant and initialization already happened + else if (toBeReplaced.containsKey(arg) && currentPosition > initializePositions.get(arg)) { + return toBeReplaced.get(arg); + } + // Var was not declared to be a constant + else { + return arg; + } + } + + + public Ast var(Ast.Var ast, Void arg) { + return getReplacement(ast); + } + + +} diff --git a/src/cd/ir/AstRewriteVisitor.java b/src/cd/ir/AstRewriteVisitor.java index a27bc62..85409b4 100644 --- a/src/cd/ir/AstRewriteVisitor.java +++ b/src/cd/ir/AstRewriteVisitor.java @@ -19,13 +19,14 @@ public class AstRewriteVisitor extends AstVisitor { } return ast; } - + /** * This method is called when a node is replaced. Subclasses can override it to do some * bookkeeping. *

* The default implementation does nothing. */ - protected void nodeReplaced(Ast oldNode, Ast newNode) {} + protected void nodeReplaced(Ast oldNode, Ast newNode) { + } } diff --git a/src/cd/ir/AstVisitor.java b/src/cd/ir/AstVisitor.java index 8d334d6..48beacf 100644 --- a/src/cd/ir/AstVisitor.java +++ b/src/cd/ir/AstVisitor.java @@ -4,18 +4,21 @@ import cd.ir.Ast.Decl; import cd.ir.Ast.Expr; import cd.ir.Ast.Stmt; -/** A visitor that visits any kind of node */ -public class AstVisitor extends ExprVisitor { - - /** - * Recurse and process {@code ast}. It is preferred to +/** + * A visitor that visits any kind of node + */ +public class AstVisitor extends ExprVisitor { + + /** + * Recurse and process {@code ast}. It is preferred to * call this rather than calling accept directly, since - * it can be overloaded to introduce memoization, - * for example. */ + * it can be overloaded to introduce memoization, + * for example. + */ public R visit(Ast ast, A arg) { return ast.accept(this, arg); } - + /** * Overrides {@link ExprVisitor#visitChildren(Expr, Object)} and * delegates to the more general {@link #visitChildren(Ast, Object)} @@ -26,11 +29,11 @@ public class AstVisitor extends ExprVisitor { public final R visitChildren(Expr ast, A arg) { return visitChildren((Ast) ast, arg); } - + /** * A handy function which visits the children of {@code ast}, * providing "arg" to each of them. It returns the result of - * the last child in the list. It is invoked by the method + * the last child in the list. It is invoked by the method * {@link #dflt(Ast, Object)} by default. */ public R visitChildren(Ast ast, A arg) { @@ -39,35 +42,39 @@ public class AstVisitor extends ExprVisitor { lastValue = visit(child, arg); return lastValue; } - - /** + + /** * The default action for default actions is to call this, * which simply recurses to any children. Also called - * by seq() by default. */ + * by seq() by default. + */ protected R dflt(Ast ast, A arg) { return visitChildren(ast, arg); } - - /** - * The default action for statements is to call this */ + + /** + * The default action for statements is to call this + */ protected R dfltStmt(Stmt ast, A arg) { return dflt(ast, arg); } - - /** - * The default action for expressions is to call this */ + + /** + * The default action for expressions is to call this + */ @Override protected R dfltExpr(Expr ast, A arg) { return dflt(ast, arg); } - /** + /** * The default action for AST nodes representing declarations - * is to call this function */ + * is to call this function + */ protected R dfltDecl(Decl ast, A arg) { return dflt(ast, arg); } - + public R assign(Ast.Assign ast, A arg) { return dfltStmt(ast, arg); } @@ -79,23 +86,23 @@ public class AstVisitor extends ExprVisitor { public R builtInWriteln(Ast.BuiltInWriteln ast, A arg) { return dfltStmt(ast, arg); } - + public R classDecl(Ast.ClassDecl ast, A arg) { return dfltDecl(ast, arg); } - + public R methodDecl(Ast.MethodDecl ast, A arg) { return dfltDecl(ast, arg); } - + public R varDecl(Ast.VarDecl ast, A arg) { return dfltDecl(ast, arg); } - + public R ifElse(Ast.IfElse ast, A arg) { return dfltStmt(ast, arg); } - + public R returnStmt(Ast.ReturnStmt ast, A arg) { return dfltStmt(ast, arg); } @@ -107,11 +114,11 @@ public class AstVisitor extends ExprVisitor { public R nop(Ast.Nop ast, A arg) { return dfltStmt(ast, arg); } - + public R seq(Ast.Seq ast, A arg) { return dflt(ast, arg); } - + public R whileLoop(Ast.WhileLoop ast, A arg) { return dfltStmt(ast, arg); } diff --git a/src/cd/ir/BasicBlock.java b/src/cd/ir/BasicBlock.java new file mode 100644 index 0000000..8cc05b6 --- /dev/null +++ b/src/cd/ir/BasicBlock.java @@ -0,0 +1,113 @@ +package cd.ir; + +import cd.ir.Ast.Expr; +import cd.ir.Ast.Stmt; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Node in a control flow graph. New instances should be created + * via the methods in {@link ControlFlowGraph}. + * Basic blocks consist of a list of statements ({@link #stmts}) which are + * executed at runtime. When the basic block ends, control flows into its + * {@link #successors}. If the block has more than one successor, it must also + * have a non-{@code null} value for {@link #condition}, which describes an expression + * that will determine which successor to take. Basic blocks also have fields + * for storing the parent and children in the dominator tree. These are generally computed + * in a second pass once the graph is fully built. + *

+ * Your team will have to write code that builds the control flow graph and computes the + * relevant dominator information. + */ +public class BasicBlock { + + /** + * Unique numerical index assigned by CFG builder between 0 and the total number of + * basic blocks. Useful for indexing into arrays and the like. + */ + public final int index; + + /** + * List of predecessor blocks in the flow graph (i.e., blocks for + * which {@code this} is a successor). + */ + public final List predecessors = new ArrayList(); + + /** + * List of successor blocks in the flow graph (those that come after the + * current block). This list is always either of size 0, 1 or 2: 1 indicates + * that control flow continues directly into the next block, and 2 indicates + * that control flow goes in one of two directions, depending on the + * value that results when {@link #condition} is evaluated at runtime. + * If there are two successors, then the 0th entry is taken when {@code condition} + * evaluates to {@code true}. + * + * @see #trueSuccessor() + * @see #falseSuccessor() + */ + public final List successors = new ArrayList(); + + /** + * List of statements in this basic block. + */ + public final List stmts = new ArrayList<>(); + + /** + * If non-null, indicates that this basic block should have + * two successors. Control flows to the first successor if + * this condition evaluates at runtime to true, otherwise to + * the second successor. If null, the basic block should have + * only one successor. + */ + public Expr condition; + + /** + * Parent of this basic block in the dominator tree (initially null until computed). + * Otherwise known as the immediate dominator. + */ + public BasicBlock dominatorTreeParent = null; + + /** + * Children of this basic block in the dominator tree (initially empty until + * computed). + */ + public final List dominatorTreeChildren = new ArrayList(); + + /** + * Contains the dominance frontier of this block. A block b is in the dominance + * frontier of another block c if c does not dominate b, but c DOES dominate a + * predecessor of b. + */ + public final Set dominanceFrontier = new HashSet(); + + public BasicBlock(int index) { + this.index = index; + } + + public BasicBlock trueSuccessor() { + assert this.condition != null; + return this.successors.get(0); + } + + public BasicBlock falseSuccessor() { + assert this.condition != null; + return this.successors.get(1); + } + + public A accept(AstVisitor visitor, B arg) { + A lastA = null; + for (Stmt stmt : stmts) + lastA = visitor.visit(stmt, arg); + if (condition != null) + lastA = visitor.visit(condition, arg); + return lastA; + } + + @Override + public String toString() { + return "BB" + index; + } +} diff --git a/src/cd/ir/CompileTimeEvaluator.java b/src/cd/ir/CompileTimeEvaluator.java new file mode 100644 index 0000000..7747d62 --- /dev/null +++ b/src/cd/ir/CompileTimeEvaluator.java @@ -0,0 +1,135 @@ +package cd.ir; + +import cd.ir.Ast.BinaryOp; +import cd.ir.Ast.BooleanConst; +import cd.ir.Ast.Expr; +import cd.ir.Ast.UnaryOp; +import cd.ir.Symbol.VariableSymbol; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static cd.Config.FALSE; +import static cd.Config.TRUE; + +/** + * A visitor that only visits {@link Expr} nodes. + * calculates the compile time value of an rhs in an assignment statement or the boolean value of a condition + */ +public class CompileTimeEvaluator extends ExprVisitor> { + + // if the expression contains non constant variables, field or index; or if the expression is a read() + // no compile Time value can be computed + private boolean failedToEvaluate; + + public Optional calc(Ast.Expr ast) { + return calc(ast, new HashMap<>()); + } + + public Optional calc(Ast.Expr ast, Map constantMap) { + try { + failedToEvaluate = false; + Integer result = visit(ast, constantMap); + if (failedToEvaluate) + return Optional.empty(); + else + return Optional.of(result); + } catch (ArithmeticException e) { + return Optional.empty(); + } + } + + @Override + protected Integer dfltExpr(Expr ast, Map arg) { + failedToEvaluate = true; + return Integer.MIN_VALUE; + } + + + @Override + public Integer binaryOp(BinaryOp ast, Map arg) { + boolean tempResult; + int left = visit(ast.left(), arg); + int right = visit(ast.right(), arg); + + switch (ast.operator) { + case B_TIMES: + return left * right; + case B_PLUS: + return left + right; + case B_MINUS: + return left - right; + case B_DIV: + return left / right; + case B_MOD: + return left % right; + case B_AND: + tempResult = (left == TRUE) && (right == TRUE); + break; + case B_OR: + tempResult = (left == TRUE) || (right == TRUE); + break; + case B_EQUAL: + tempResult = left == right; + break; + case B_NOT_EQUAL: + tempResult = left != right; + break; + case B_LESS_THAN: + tempResult = left < right; + break; + case B_LESS_OR_EQUAL: + tempResult = left <= right; + break; + case B_GREATER_THAN: + tempResult = left > right; + break; + case B_GREATER_OR_EQUAL: + tempResult = left >= right; + break; + default: + throw new RuntimeException("Invalid binary operator"); + } + return tempResult ? TRUE : FALSE; + } + + + @Override + public Integer unaryOp(UnaryOp ast, Map arg) { + int result = visit(ast.arg(), arg); + + switch (ast.operator) { + case U_PLUS: + return result; + case U_MINUS: + return -result; + case U_BOOL_NOT: + return result == FALSE ? TRUE : FALSE; + default: + throw new RuntimeException("Invalid unary operator"); + } + } + + @Override + public Integer booleanConst(BooleanConst ast, Map arg) { + return ast.value ? TRUE : FALSE; + } + + @Override + public Integer intConst(Ast.IntConst ast, Map arg) { + return ast.value; + } + + // check if a given Variable has a constant value + @Override + public Integer var(Ast.Var ast, Map arg) { + if (arg.containsKey(ast.sym)) { + return arg.get(ast.sym); + } else { + failedToEvaluate = true; + return Integer.MIN_VALUE; + } + } + +} diff --git a/src/cd/ir/ControlFlowGraph.java b/src/cd/ir/ControlFlowGraph.java new file mode 100644 index 0000000..b72be15 --- /dev/null +++ b/src/cd/ir/ControlFlowGraph.java @@ -0,0 +1,61 @@ +package cd.ir; + +import cd.ir.Ast.Expr; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents the control flow graph of a single method. + */ +public class ControlFlowGraph { + public BasicBlock start, end; + public final List allBlocks = new ArrayList(); + + public int count() { + return allBlocks.size(); + } + + public BasicBlock newBlock() { + BasicBlock blk = new BasicBlock(count()); + allBlocks.add(blk); + return blk; + } + + /** + * Given a list of basic blocks that do not yet have successors, + * merges their control flows into a single successor and returns + * the new successor. + */ + public BasicBlock join(BasicBlock... pred) { + BasicBlock result = newBlock(); + for (BasicBlock p : pred) { + assert p.condition == null; + assert p.successors.size() == 0; + p.successors.add(result); + result.predecessors.add(p); + } + return result; + } + + /** + * Terminates {@code blk} so that it evaluates {@code cond}, + * and creates two new basic blocks, one for the case where + * the result is true, and one for the case where the result is + * false. + */ + public void terminateInCondition(BasicBlock blk, Expr cond) { + assert blk.condition == null; + assert blk.successors.size() == 0; + blk.condition = cond; + blk.successors.add(newBlock()); + blk.successors.add(newBlock()); + blk.trueSuccessor().predecessors.add(blk); + blk.falseSuccessor().predecessors.add(blk); + } + + public void connect(BasicBlock from, BasicBlock to) { + to.predecessors.add(from); + from.successors.add(to); + } +} diff --git a/src/cd/ir/ExprVisitor.java b/src/cd/ir/ExprVisitor.java index 121fd57..746a1d4 100644 --- a/src/cd/ir/ExprVisitor.java +++ b/src/cd/ir/ExprVisitor.java @@ -5,16 +5,17 @@ import cd.ir.Ast.Expr; /** * A visitor that only visits {@link Expr} nodes. */ -public class ExprVisitor { - /** - * Recurse and process {@code ast}. It is preferred to +public class ExprVisitor { + /** + * Recurse and process {@code ast}. It is preferred to * call this rather than calling accept directly, since - * it can be overloaded to introduce memoization, - * for example. */ + * it can be overloaded to introduce memoization, + * for example. + */ public R visit(Expr ast, A arg) { return ast.accept(this, arg); } - + /** * Visits all children of the expression. Relies on the fact * that {@link Expr} nodes only contain other {@link Expr} nodes. @@ -22,18 +23,19 @@ public class ExprVisitor { public R visitChildren(Expr ast, A arg) { R lastValue = null; for (Ast child : ast.children()) - lastValue = visit((Expr)child, arg); + lastValue = visit((Expr) child, arg); return lastValue; } - - /** + + /** * The default action for default actions is to call this, * which simply recurses to any children. Also called - * by seq() by default. */ + * by seq() by default. + */ protected R dfltExpr(Expr ast, A arg) { return visitChildren(ast, arg); } - + public R binaryOp(Ast.BinaryOp ast, A arg) { return dfltExpr(ast, arg); } @@ -41,15 +43,15 @@ public class ExprVisitor { public R booleanConst(Ast.BooleanConst ast, A arg) { return dfltExpr(ast, arg); } - + public R builtInRead(Ast.BuiltInRead ast, A arg) { return dfltExpr(ast, arg); } - + public R cast(Ast.Cast ast, A arg) { return dfltExpr(ast, arg); } - + public R field(Ast.Field ast, A arg) { return dfltExpr(ast, arg); } @@ -61,7 +63,7 @@ public class ExprVisitor { public R intConst(Ast.IntConst ast, A arg) { return dfltExpr(ast, arg); } - + public R methodCall(Ast.MethodCallExpr ast, A arg) { return dfltExpr(ast, arg); } @@ -73,7 +75,7 @@ public class ExprVisitor { public R newArray(Ast.NewArray ast, A arg) { return dfltExpr(ast, arg); } - + public R nullConst(Ast.NullConst ast, A arg) { return dfltExpr(ast, arg); } diff --git a/src/cd/ir/Symbol.java b/src/cd/ir/Symbol.java index b2f5502..ad57946 100644 --- a/src/cd/ir/Symbol.java +++ b/src/cd/ir/Symbol.java @@ -1,43 +1,45 @@ package cd.ir; +import cd.backend.codegen.AstCodeGenerator; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public abstract class Symbol { - + public final String name; - + public static abstract class TypeSymbol extends Symbol { - + public TypeSymbol(String name) { super(name); } public abstract boolean isReferenceType(); - + @Override - public String toString() { + public String toString() { return name; } - + public abstract TypeSymbol getSuperType(); - + public boolean isSuperTypeOf(TypeSymbol sub) { - // "void" is not a subtype of any type not even itself - if(this == PrimitiveTypeSymbol.voidType || sub == PrimitiveTypeSymbol.voidType) - return false; - + // "void" is not a subtype of any type not even itself + if (this == PrimitiveTypeSymbol.voidType || sub == PrimitiveTypeSymbol.voidType) + return false; + if (sub == this) return true; - + if (this instanceof PrimitiveTypeSymbol || sub instanceof PrimitiveTypeSymbol) return false; // no hierarchy with primitive types - + if (sub == ClassSymbol.nullType && this.isReferenceType()) return true; - + TypeSymbol curr = sub; while (curr != null) { if (curr == this) @@ -46,94 +48,106 @@ public abstract class Symbol { } return false; } - + } - + public static class PrimitiveTypeSymbol extends TypeSymbol { - - /** Symbols for the built-in primitive types */ + + /** + * Symbols for the built-in primitive types + */ public static final PrimitiveTypeSymbol intType = new PrimitiveTypeSymbol("int"); public static final PrimitiveTypeSymbol voidType = new PrimitiveTypeSymbol("void"); public static final PrimitiveTypeSymbol booleanType = new PrimitiveTypeSymbol("boolean"); public PrimitiveTypeSymbol(String name) { super(name); - } - + } + @Override - public boolean isReferenceType() { + public boolean isReferenceType() { return false; } - + @Override - public TypeSymbol getSuperType() { + public TypeSymbol getSuperType() { throw new RuntimeException("should not call this on PrimitiveTypeSymbol"); } } - + public static class ArrayTypeSymbol extends TypeSymbol { public final TypeSymbol elementType; - + public ArrayTypeSymbol(TypeSymbol elementType) { - super(elementType.name+"[]"); + super(elementType.name + "[]"); this.elementType = elementType; } - + @Override - public boolean isReferenceType() { + public boolean isReferenceType() { return true; } - + @Override - public TypeSymbol getSuperType() { + public TypeSymbol getSuperType() { return ClassSymbol.objectType; } - + } - + public static class ClassSymbol extends TypeSymbol { public final Ast.ClassDecl ast; public ClassSymbol superClass; public final VariableSymbol thisSymbol = - new VariableSymbol("this", this); - public final Map fields = - new HashMap(); + new VariableSymbol("this", this); + public final Map fields = + new HashMap(); public final Map methods = - new HashMap(); + new HashMap(); - /** Symbols for the built-in Object and null types */ + public int totalMethods = -1; + + public int totalFields = -1; + + public int sizeof = -1; + + /** + * Symbols for the built-in Object and null types + */ public static final ClassSymbol nullType = new ClassSymbol(""); - public static final ClassSymbol objectType = new ClassSymbol("Object"); - + public static final ClassSymbol objectType = new ClassSymbol("Object"); + public ClassSymbol(Ast.ClassDecl ast) { super(ast.name); this.ast = ast; } - - /** Used to create the default {@code Object} - * and {@code } types */ + + /** + * Used to create the default {@code Object} + * and {@code } types + */ public ClassSymbol(String name) { super(name); this.ast = null; } - + @Override - public boolean isReferenceType() { + public boolean isReferenceType() { return true; } - + @Override - public TypeSymbol getSuperType() { + public TypeSymbol getSuperType() { return superClass; } - + public VariableSymbol getField(String name) { VariableSymbol fsym = fields.get(name); if (fsym == null && superClass != null) return superClass.getField(name); return fsym; } - + public MethodSymbol getMethod(String name) { MethodSymbol msym = methods.get(name); if (msym == null && superClass != null) @@ -143,32 +157,53 @@ public abstract class Symbol { } public static class MethodSymbol extends Symbol { - + public final Ast.MethodDecl ast; public final Map locals = - new HashMap(); + new HashMap(); public final List parameters = - new ArrayList(); - + new ArrayList(); + public TypeSymbol returnType; - + + public ClassSymbol owner; + + public int vtableIndex = -1; + + public MethodSymbol overrides; + public MethodSymbol(Ast.MethodDecl ast) { super(ast.name); this.ast = ast; } - + @Override - public String toString() { + public String toString() { return name + "(...)"; } } - + public static class VariableSymbol extends Symbol { - - public static enum Kind { PARAM, LOCAL, FIELD }; + + public static enum Kind {PARAM, LOCAL, FIELD} + + ; public final TypeSymbol type; public final Kind kind; - + + /** + * Meaning depends on the kind of variable, but generally refers + * to the offset in bytes from some base ptr to where the variable + * is found. + *

+ * Computed in {@link AstCodeGenerator}. + */ + public int offset = -1; + public VariableSymbol(String name, TypeSymbol type) { this(name, type, Kind.PARAM); } @@ -176,11 +211,11 @@ public abstract class Symbol { public VariableSymbol(String name, TypeSymbol type, Kind kind) { super(name); this.type = type; - this.kind = kind; + this.kind = kind; } - + @Override - public String toString() { + public String toString() { return name; } } diff --git a/src/cd/transform/AstOptimizer.java b/src/cd/transform/AstOptimizer.java new file mode 100644 index 0000000..e7aba03 --- /dev/null +++ b/src/cd/transform/AstOptimizer.java @@ -0,0 +1,46 @@ +package cd.transform; + +import cd.ir.Ast.ClassDecl; +import cd.ir.Ast.MethodDecl; +import cd.ir.Symbol.TypeSymbol; +import cd.transform.optimizers.*; + +import java.util.List; + +public class AstOptimizer { + private static final IOptimizer[] optimizers = { + new TrivialOpRemoval(), // 100% accuracy + new BranchElimination(), // 100% accuracy + new BlockRemoval(), // 100% accuracy + new ConstantPropagator(),// 98% accuracy on its own +// new WriteOnlyVarRemoval(), + }; + + private static final IGlobalOptimizer[] globalOptimizers = { +// new UnusedClassRemoval(),// 98% accuracy on its own +// new UnusedMethodRemoval(), + }; + + public void go(List classDeclList, List allTypeSymbols) { + // Local optimizations inside methods/blocks always comes first + for (ClassDecl classDecl : classDeclList) { + for (MethodDecl methodDecl : classDecl.methods()) { + boolean changed = true; + while (changed) { + changed = false; + for (IOptimizer optimizer : optimizers) { + changed |= optimizer.go(methodDecl.cfg); + } + } + } + } + + boolean globalChange = true; + while (globalChange) { + globalChange = false; + for (IGlobalOptimizer globalOptimizer : globalOptimizers) { + globalChange |= globalOptimizer.go(classDeclList, allTypeSymbols); + } + } + } +} diff --git a/src/cd/transform/CfgBuilder.java b/src/cd/transform/CfgBuilder.java new file mode 100644 index 0000000..d2d7457 --- /dev/null +++ b/src/cd/transform/CfgBuilder.java @@ -0,0 +1,85 @@ +package cd.transform; + +import cd.ir.Ast; +import cd.ir.Ast.*; +import cd.ir.AstVisitor; +import cd.ir.BasicBlock; +import cd.ir.ControlFlowGraph; + +public class CfgBuilder { + + ControlFlowGraph cfg; + + public void build(MethodDecl mdecl) { + cfg = mdecl.cfg = new ControlFlowGraph(); + cfg.start = cfg.newBlock(); // Note: Use newBlock() to create new basic blocks + cfg.end = cfg.newBlock(); // unique exit block to which all blocks that end with a return stmt. lead + + { + BasicBlock lastInBody = new Visitor().visit(mdecl.body(), cfg.start); + if (lastInBody != null) cfg.connect(lastInBody, cfg.end); + } + + // CFG and AST are not synchronized, only use CFG from now on + mdecl.setBody(null); + } + + protected class Visitor extends AstVisitor { + + @Override + protected BasicBlock dfltStmt(Stmt ast, BasicBlock arg) { + if (arg == null) return null; // dead code, no need to generate anything + arg.stmts.add(ast); + return arg; + } + + @Override + public BasicBlock ifElse(IfElse ast, BasicBlock arg) { + if (arg == null) return null; // dead code, no need to generate anything + cfg.terminateInCondition(arg, ast.condition()); + BasicBlock then = visit(ast.then(), arg.trueSuccessor()); + BasicBlock otherwise = visit(ast.otherwise(), arg.falseSuccessor()); + if (then != null && otherwise != null) { + return cfg.join(then, otherwise); + } else if (then != null) { + BasicBlock newBlock = cfg.newBlock(); + cfg.connect(then, newBlock); + return newBlock; + } else if (otherwise != null) { + BasicBlock newBlock = cfg.newBlock(); + cfg.connect(otherwise, newBlock); + return newBlock; + } else { + return null; + } + } + + @Override + public BasicBlock seq(Seq ast, BasicBlock arg_) { + BasicBlock arg = arg_; + for (Ast child : ast.children()) + arg = this.visit(child, arg); + return arg; + } + + @Override + public BasicBlock whileLoop(WhileLoop ast, BasicBlock arg) { + if (arg == null) return null; // dead code, no need to generate anything + BasicBlock cond = cfg.join(arg); + cfg.terminateInCondition(cond, ast.condition()); + BasicBlock body = visit(ast.body(), cond.trueSuccessor()); + if (body != null) cfg.connect(body, cond); + return cond.falseSuccessor(); + } + + @Override + public BasicBlock returnStmt(Ast.ReturnStmt ast, BasicBlock arg) { + if (arg == null) return null; // dead code, no need to generate anything + arg.stmts.add(ast); + cfg.connect(arg, cfg.end); + return null; // null means that this block leads nowhere else + } + + } + +} diff --git a/src/cd/transform/analysis/ArraySizeAnalysis.java b/src/cd/transform/analysis/ArraySizeAnalysis.java new file mode 100644 index 0000000..c8d2cac --- /dev/null +++ b/src/cd/transform/analysis/ArraySizeAnalysis.java @@ -0,0 +1,51 @@ +package cd.transform.analysis; + +import cd.ir.Ast; +import cd.ir.Ast.Assign; +import cd.ir.CompileTimeEvaluator; +import cd.ir.ControlFlowGraph; +import cd.ir.Symbol.VariableSymbol; + +import java.util.Map; +import java.util.Optional; + +public class ArraySizeAnalysis extends MaybeFlowAnalysis { + + public ArraySizeAnalysis(ControlFlowGraph cfg) { + super(cfg, false, true); + } + + @Override + protected MaybeC defaultInit(VariableSymbol varSym) { + return MaybeC.notConstant(); + } + + @Override + public void transferStmt(Ast.Stmt stmt, Map> state) { + CompileTimeEvaluator cte = new CompileTimeEvaluator(); + if (stmt instanceof Assign) { + Assign assign = (Assign) stmt; + if (assign.right() instanceof Ast.NewArray) { + Ast.NewArray newArray = (Ast.NewArray) assign.right(); + VariableSymbol sym = getVarSym(assign.left()); + if (sym == null) return; + Optional arraySize = cte.calc(newArray.arg()); + if (arraySize.isPresent()) { + state.put(sym, MaybeC.value(arraySize.get())); + } else { + state.get(sym).setNotConstant(); + } + } + } + } + + private VariableSymbol getVarSym(Ast.Expr expr) { + if (expr instanceof Ast.Var) { + return ((Ast.Var) expr).sym; + } else if (expr instanceof Ast.Field) { + return ((Ast.Field) expr).sym; + } + return null; + } + +} diff --git a/src/cd/transform/analysis/AssignmentAnalysis.java b/src/cd/transform/analysis/AssignmentAnalysis.java new file mode 100755 index 0000000..cb64513 --- /dev/null +++ b/src/cd/transform/analysis/AssignmentAnalysis.java @@ -0,0 +1,101 @@ +package cd.transform.analysis; + +import cd.ir.Ast; +import cd.ir.Ast.Assign; +import cd.ir.Ast.NewArray; +import cd.ir.Ast.NewObject; +import cd.ir.CompileTimeEvaluator; +import cd.ir.ControlFlowGraph; +import cd.ir.ExprVisitor; +import cd.ir.Symbol.VariableSymbol; +import cd.transform.analysis.NullAnalysis.NullVisitor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; + + + +public class AssignmentAnalysis extends MaybeFlowAnalysis> { + + public AssignmentAnalysis(ControlFlowGraph cfg) { + super(cfg, false, true); + } + + @Override + protected MaybeC> defaultInit(VariableSymbol varSym) { + List value = new ArrayList(); + return MaybeC.value(value); + } + + /* + * the state contains all assignments that are unused and can be removed + * the last assign stmt in the list represents the current assignment of a variable + * + * if assignment: check lhs: + * if lhs is variable, put assign stmt in state of the variable + * meaning that this assign stmt was not used yet + * visit rhs to check for used variables + * + * if not assignment, visit ast node for all var symbols and remove the last assign stmt if var was used + */ + @Override + public void transferStmt(Ast.Stmt stmt, Map>> state) { + if (stmt instanceof Assign) { + Assign assign = (Assign) stmt; + if (assign.right() instanceof Ast.Var) { + Ast.Var var = (Ast.Var) assign.left(); + VariableSymbol sym = var.sym; + if (sym == null) return; + MaybeC> maybeStateList = state.get(sym); + List stateList = maybeStateList.getValue(); + stateList.add(assign); + MaybeC> newMaybeStateList = MaybeC.value(stateList); + state.put(sym, newMaybeStateList); + } + removeAssignStmt(assign.right(), state); + return; + } + // TODO: if not assign stmt visit node and check for used variables + if (stmt instanceof Ast.BuiltInWrite) { + Ast.BuiltInWrite write = (Ast.BuiltInWrite) stmt; + removeAssignStmt(write.arg(), state); + } + } + + // iterates over all var symbols and checks if one was used + // if so, remove last assignment + public void removeAssignStmt(Ast.Expr ast, Map>> state) { + for (VariableSymbol varSym: state.keySet()) { + Boolean isused = new varVisitor().visit(ast, varSym); + if (isused) { + MaybeC> maybeStateList = state.get(varSym); + List stateList = maybeStateList.getValue(); + if (!stateList.isEmpty()) { + int end = stateList.size() - 1; + stateList.remove(end); + } + MaybeC> newMaybeStateList = MaybeC.value(stateList); + state.put(varSym, newMaybeStateList); + } + } + return; + } + + + + +// returns true if a given variable is used inside an Ast node +static class varVisitor extends ExprVisitor { + + @Override + public Boolean var(Ast.Var ast, VariableSymbol var) { + return ast.sym == var; + } + +} + +} diff --git a/src/cd/transform/analysis/ClassUsage.java b/src/cd/transform/analysis/ClassUsage.java new file mode 100644 index 0000000..42e29b2 --- /dev/null +++ b/src/cd/transform/analysis/ClassUsage.java @@ -0,0 +1,76 @@ +package cd.transform.analysis; + +import cd.ir.Ast.*; +import cd.ir.AstVisitor; +import cd.ir.Symbol.ArrayTypeSymbol; +import cd.ir.Symbol.ClassSymbol; +import cd.ir.Symbol.TypeSymbol; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ClassUsage extends AstVisitor> { + + public Set go(List classDecls) { + Set used = new HashSet<>(); + Set todo = new HashSet<>(); + for (ClassDecl classDecl : classDecls) + if (classDecl.name.equals("Main")) + todo.add(classDecl.sym); + + while (!todo.isEmpty()) { + ClassSymbol sym = todo.iterator().next(); + todo.remove(sym); + if (!used.contains(sym.ast) && sym != ClassSymbol.objectType) { + visit(sym.ast, todo); + used.add(sym.ast); + } + } + + return used; + } + + @Override + public Void classDecl(ClassDecl ast, Set arg) { + arg.add(ast.sym.superClass); + return super.classDecl(ast, arg); + } + + @Override + protected Void dfltExpr(Expr ast, Set arg) { + queueIfClassSymbol(ast.type, arg); + return super.dfltExpr(ast, arg); + } + + @Override + public Void newObject(NewObject ast, Set arg) { + arg.add((ClassSymbol) ast.type); + return super.newObject(ast, arg); + } + + @Override + public Void newArray(NewArray ast, Set arg) { + queueIfClassSymbol(ast.type, arg); + return super.newArray(ast, arg); + } + + @Override + public Void varDecl(VarDecl ast, Set arg) { + queueIfClassSymbol(ast.sym.type, arg); + return super.varDecl(ast, arg); + } + + @Override + public Void methodDecl(MethodDecl ast, Set arg) { + queueIfClassSymbol(ast.sym.returnType, arg); + return super.methodDecl(ast, arg); + } + + private void queueIfClassSymbol(TypeSymbol type, Set arg) { + if (type instanceof ArrayTypeSymbol) + type = ((ArrayTypeSymbol) type).elementType; + if (type instanceof ClassSymbol) + arg.add((ClassSymbol) type); + } +} diff --git a/src/cd/transform/analysis/ConstantAnalysis.java b/src/cd/transform/analysis/ConstantAnalysis.java new file mode 100755 index 0000000..af129c4 --- /dev/null +++ b/src/cd/transform/analysis/ConstantAnalysis.java @@ -0,0 +1,50 @@ +package cd.transform.analysis; + + +import cd.ir.Ast; +import cd.ir.Ast.Assign; +import cd.ir.Ast.Var; +import cd.ir.ControlFlowGraph; +import cd.ir.Symbol.VariableSymbol; + +import java.util.Map; +import java.util.Optional; + +/** + * This class implements constant propagation: + * remember variables that have constant values + * no need evaluate them at runtime, save movl instruction + */ +public class ConstantAnalysis extends MaybeFlowAnalysis { + public ConstantAnalysis(ControlFlowGraph cfg) { + super(cfg, true, false); + } + + @Override + protected MaybeC defaultInit(VariableSymbol varSym) { + if (varSym.kind == VariableSymbol.Kind.LOCAL) + return MaybeC.value(0); + else + return MaybeC.notConstant(); + } + + @Override + public void transferStmt(Ast.Stmt stmt, Map> state) { + if (stmt instanceof Assign) { + Assign ast = (Assign) stmt; + if (ast.left() instanceof Var) { + VariableSymbol symbol = ((Var) ast.left()).sym; + if (!state.containsKey(symbol)) return; + + // Obtain value and previous MaybeConstant + Optional valueOpt = cte.calc(ast.right()); + MaybeC mc = state.get(symbol); + if (valueOpt.isPresent()) { + state.put(symbol, MaybeC.value(valueOpt.get())); + } else { + mc.setNotConstant(); + } + } + } + } +} diff --git a/src/cd/transform/analysis/DataFlowAnalysis.java b/src/cd/transform/analysis/DataFlowAnalysis.java new file mode 100644 index 0000000..c0e4093 --- /dev/null +++ b/src/cd/transform/analysis/DataFlowAnalysis.java @@ -0,0 +1,189 @@ +package cd.transform.analysis; + +import cd.ir.BasicBlock; +import cd.ir.ControlFlowGraph; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.unmodifiableMap; + +/** + * The abstract superclass of all data-flow analyses. This class provides a framework to + * implement concrete analyses by providing {@link #initialState()}, + * {@link #startState()}, {@link #transferFunction(BasicBlock, Object)}, and + * {@link #join(Set)} methods. + * + * @param The type of states the analysis computes, specified by a concrete subclass. + * Typically, this is a set or map type. + */ +public abstract class DataFlowAnalysis { + + protected final ControlFlowGraph cfg; + Map inStates; + Map outStates; + + public DataFlowAnalysis(ControlFlowGraph cfg) { + this.cfg = cfg; + } + + /** + * Returns the in-state of basic block block. + */ + public State inStateOf(BasicBlock block) { + return inStates.getOrDefault(block, startState()); + } + + /** + * Returns the out-state of basic block block. + */ + public State outStateOf(BasicBlock block) { + return outStates.get(block); + } + + protected void emptyStates() { + inStates = new HashMap<>(); + outStates = new HashMap<>(); + } + + /** + * Do forward flow fixed-point iteration until out-states do not change anymore. + * Subclasses should call this method in their constructor after the required + * initialization. + */ + protected void iterate() { + inStates = new HashMap<>(); + outStates = new HashMap<>(); + for (BasicBlock block : cfg.allBlocks) + outStates.put(block, initialState()); + + Set todo = new HashSet<>(); + todo.addAll(cfg.allBlocks); + while (!todo.isEmpty()) { + BasicBlock block = todo.iterator().next(); + todo.remove(block); + + /* calculate in-state */ + State inState; + if (block == cfg.start) + inState = startState(); + else { + Set predOutStates = new HashSet<>(); + for (BasicBlock pred : block.predecessors) + predOutStates.add(outStates.get(pred)); + inState = join(predOutStates); + } + inStates.put(block, inState); + + State newOutState = transferFunction(block, inState); + + /* if out-state changed, recalculate successors */ + if (!newOutState.equals(outStates.get(block))) { + outStates.put(block, newOutState); + todo.addAll(block.successors); + } + } + outStates = unmodifiableMap(outStates); + } + + /** + * Returns the initial state for all blocks except the {@link ControlFlowGraph#start start} + * block. + */ + protected abstract State initialState(); + + /** + * Returns the initial state for the {@link ControlFlowGraph#start start} block. + */ + protected abstract State startState(); + + /** + * Calculates the out-state for a basic block block and an in-state + * inState + */ + protected abstract State transferFunction(BasicBlock block, State inState); + + /** + * Merges together several out-states and returns the in-state for the transfer function. + */ + protected abstract State join(Set states); + + /** + * DataFlowAnalysis implementation with reversed direction. The methods are overridden in + * order to reflect the change in the parameters' names and in the javadoc. + */ + public static abstract class Reverse extends DataFlowAnalysis { + public Reverse(ControlFlowGraph cfg) { + super(cfg); + } + + /** + * Do backward flow fixed-point iteration until in-states do not change anymore. + * Subclasses should call this method in their constructor after the required + * initialization. + */ + @Override + protected void iterate() { + inStates = new HashMap<>(); + outStates = new HashMap<>(); + for (BasicBlock block : cfg.allBlocks) + inStates.put(block, initialState()); + + Set todo = new HashSet<>(); + todo.addAll(cfg.allBlocks); + while (!todo.isEmpty()) { + BasicBlock block = todo.iterator().next(); + todo.remove(block); + + /* calculate out-state */ + A outState; + if (block == cfg.end) + outState = startState(); + else { + Set predInStates = new HashSet<>(); + for (BasicBlock pred : block.successors) + predInStates.add(inStates.get(pred)); + outState = join(predInStates); + } + outStates.put(block, outState); + + A newInState = transferFunction(block, outState); + + /* if in-state changed, recalculate successors */ + if (!newInState.equals(inStates.get(block))) { + inStates.put(block, newInState); + todo.addAll(block.predecessors); + } + } + inStates = unmodifiableMap(inStates); + } + + /** + * Returns the initial state for all blocks except the {@link ControlFlowGraph#end end} + * block. + */ + @Override + protected abstract A initialState(); + + /** + * Returns the initial state for the {@link ControlFlowGraph#end end} block. + */ + @Override + protected abstract A startState(); + + /** + * Calculates the in-state for a basic block block and an out-state + * outState + */ + @Override + protected abstract A transferFunction(BasicBlock block, A outState); + + /** + * Merges together several in-states and returns the out-state for the transfer function. + */ + @Override + protected abstract A join(Set states); + } +} diff --git a/src/cd/transform/analysis/DynamicTypeAnalysis.java b/src/cd/transform/analysis/DynamicTypeAnalysis.java new file mode 100644 index 0000000..5db8a5c --- /dev/null +++ b/src/cd/transform/analysis/DynamicTypeAnalysis.java @@ -0,0 +1,50 @@ +package cd.transform.analysis; + +import cd.ir.Ast; +import cd.ir.Ast.Assign; +import cd.ir.Ast.Var; +import cd.ir.ControlFlowGraph; +import cd.ir.ExprVisitor; +import cd.ir.Symbol; +import cd.ir.Symbol.TypeSymbol; + +import java.util.Map; + +public class DynamicTypeAnalysis extends MaybeFlowAnalysis { + public DynamicTypeAnalysis(ControlFlowGraph cfg) { + super(cfg, false, true); + } + + @Override + public void transferStmt(Ast.Stmt stmt, Map> state) { + if (stmt instanceof Assign) { + Assign assign = (Assign) stmt; + if (!(assign.left() instanceof Var)) return; + Symbol.VariableSymbol sym = ((Var) assign.left()).sym; + if (!state.containsKey(sym)) return; + TypeSymbol type = new TypeVisitor().visit(assign.right(), null); + if (type != null) { + state.put(sym, MaybeC.value(type)); + } else { + state.get(sym).setNotConstant(); + } + } + } + + @Override + protected MaybeC defaultInit(Symbol.VariableSymbol varSym) { + return MaybeC.notConstant(); + } + + static class TypeVisitor extends ExprVisitor { + @Override + public TypeSymbol newObject(Ast.NewObject ast, Void arg) { + return ast.type; + } + + @Override + public TypeSymbol newArray(Ast.NewArray ast, Void arg) { + return ast.type; + } + } +} diff --git a/src/cd/transform/analysis/LiveVarAnalysis.java b/src/cd/transform/analysis/LiveVarAnalysis.java new file mode 100644 index 0000000..da28a8a --- /dev/null +++ b/src/cd/transform/analysis/LiveVarAnalysis.java @@ -0,0 +1,92 @@ +package cd.transform.analysis; + +import cd.ir.Ast; +import cd.ir.Ast.Assign; +import cd.ir.AstVisitor; +import cd.ir.BasicBlock; +import cd.ir.ControlFlowGraph; +import cd.ir.Symbol.VariableSymbol; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class LiveVarAnalysis extends DataFlowAnalysis.Reverse> { + private Map> killSets = new HashMap<>(); + private Map> genSets = new HashMap<>(); + + public LiveVarAnalysis(ControlFlowGraph cfg) { + super(cfg); + + // For faster iteration, the transfer function will just use kill and gen sets + for (BasicBlock block : cfg.allBlocks) + killGenSetOf(block); + + iterate(); + } + + private void killGenSetOf(BasicBlock block) { + Set kill = new HashSet<>(); + Set gen = new HashSet<>(); + + if (block.condition != null) + new Visitor().visit(block.condition, gen); + for (int i = block.stmts.size() - 1; i >= 0; i--) { + VariableSymbol var = new Visitor().visit(block.stmts.get(i), gen); + if (var != null) { + // TODO: review this code + kill.add(var); + gen.remove(var); + } + } + + killSets.put(block, kill); + genSets.put(block, gen); + } + + @Override + protected Set initialState() { + return new HashSet<>(); + } + + @Override + protected Set startState() { + return new HashSet<>(); + } + + @Override + protected Set transferFunction(BasicBlock block, Set inState) { + Set result = new HashSet<>(inState); + result.removeAll(killSets.get(block)); + result.addAll(genSets.get(block)); + return result; + } + + @Override + protected Set join(Set> states) { + Set result = new HashSet<>(); + states.forEach(result::addAll); + return result; + } + + public static class Visitor extends AstVisitor> { + @Override + public VariableSymbol assign(Assign ast, Set arg) { + visit(ast.right(), arg); + if (ast.left() instanceof Ast.Var) + return visit(ast.left(), arg); + else + return null; + } + + @Override + public VariableSymbol var(Ast.Var ast, Set arg) { + if (!ast.sym.type.isReferenceType()) { + arg.add(ast.sym); + return ast.sym; + } + return null; + } + } +} diff --git a/src/cd/transform/analysis/MaybeC.java b/src/cd/transform/analysis/MaybeC.java new file mode 100644 index 0000000..07b861e --- /dev/null +++ b/src/cd/transform/analysis/MaybeC.java @@ -0,0 +1,112 @@ +package cd.transform.analysis; + + +public class MaybeC { + private E value; + private boolean anyValue; + + static MaybeC anyValue() { + return new MaybeC<>(null, true); + } + + static MaybeC notConstant() { + return new MaybeC<>(null, false); + } + + static MaybeC value(T value) { + return new MaybeC<>(value, false); + } + + private MaybeC(E value, boolean anyValue) { + this.value = value; + this.anyValue = anyValue; + } + + MaybeC(MaybeC base) { + this.value = base.value; + this.anyValue = base.anyValue; + } + + public void setValue(E value) { + assert isAnyValue() || isConstant(); + assert value != null; + anyValue = false; + this.value = value; + } + + public E getValue() { + assert isConstant(); + return value; + } + + public boolean isAnyValue() { + return anyValue && value == null; + } + + public boolean isNotConstant() { + return !anyValue && value == null; + } + + public boolean isConstant() { + return !anyValue && value != null; + } + + public void setNotConstant() { + anyValue = false; + value = null; + } + + public void setAnyValue() { + assert isAnyValue(); + } + + /** + * Updates the current object, following the table:
+ * + * + * + * + * + *
notConstantv1anyValue
notConstantnotConstantnotConstantnotConstant
v2notConstantv1==v2?v1:notConstantv2
anyValuenotConstantv1anyValue
+ */ + public void joinWith(MaybeC other) { + if (this.isNotConstant() || other.isNotConstant()) { + setNotConstant(); + } else if (!this.isAnyValue() || !other.isAnyValue()) { + if (this.isAnyValue()) + setValue(other.getValue()); // Copy the other's value + else if (!other.isAnyValue() && this.getValue() != other.getValue()) + setNotConstant(); // Values don't match + } + } + + + @Override + public boolean equals(Object obj) { + if (obj instanceof MaybeC) { + MaybeC other = (MaybeC) obj; + if (isNotConstant() && other.isNotConstant()) + return true; + if (isAnyValue() && other.isAnyValue()) + return true; + if (isConstant() && other.isConstant()) { + if (value == null || other.value == null) { + return value == other.value; + } else { + return value.equals(other.value); + } + } + } + return false; + } + + + @Override + public String toString() { + if (isAnyValue()) return "any"; + if (isNotConstant()) return "none"; + if (isConstant()) return getValue().toString(); + throw new RuntimeException("MaybeC in erroneous state"); + } +} + diff --git a/src/cd/transform/analysis/MaybeFlowAnalysis.java b/src/cd/transform/analysis/MaybeFlowAnalysis.java new file mode 100644 index 0000000..c4570f2 --- /dev/null +++ b/src/cd/transform/analysis/MaybeFlowAnalysis.java @@ -0,0 +1,115 @@ +package cd.transform.analysis; + +import cd.ir.*; +import cd.ir.Ast.Var; +import cd.ir.Symbol.VariableSymbol; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public abstract class MaybeFlowAnalysis extends DataFlowAnalysis>> { + protected final CompileTimeEvaluator cte = new CompileTimeEvaluator(); + private final Set allVars = new HashSet<>(); + + public MaybeFlowAnalysis(ControlFlowGraph cfg, boolean primitiveVars, boolean referenceVars) { + super(cfg); + + // Obtain all variables + for (BasicBlock block : cfg.allBlocks) { + VarVisitor visitor = new VarVisitor(primitiveVars, referenceVars); + block.accept(visitor, allVars); + } + + if (!allVars.isEmpty()) + iterate(); + else + emptyStates(); + } + + public Map inStateConstantsOf(BasicBlock block) { + return getConstants(inStateOf(block)); + } + + public Map outStateConstantsOf(BasicBlock block) { + return getConstants(outStateOf(block)); + } + + private Map getConstants(Map> map) { + Map result = new HashMap<>(); + if (map == null) return result; + map.forEach((key, value) -> { + if (value.isConstant()) + result.put(key, value.getValue()); + }); + return result; + } + + /** + * Reads a statement and updates the current state accordingly, + */ + public abstract void transferStmt(Ast.Stmt stmt, Map> state); + + /** + * Default value for each constant at the beginning of the start block + */ + protected abstract MaybeC defaultInit(VariableSymbol varSym); + + @Override + protected Map> initialState() { + Map> map = new HashMap<>(); + allVars.forEach(v -> map.put(v, MaybeC.anyValue())); + return map; + } + + @Override + protected Map> startState() { + Map> map = new HashMap<>(); + allVars.forEach(v -> map.put(v, defaultInit(v))); + return map; + } + + @Override + protected Map> transferFunction(BasicBlock block, Map> inState) { + Map> outState = new HashMap<>(); + inState.forEach((key, value) -> outState.put(key, new MaybeC<>(value))); + for (Ast.Stmt stmt : block.stmts) + transferStmt(stmt, outState); + return outState; + } + + @Override + protected Map> join(Set>> states) { + // The basic condition is that the value must be the same in all maps for the constant to be included + Map> result = new HashMap<>(); + for (VariableSymbol sym : allVars) { + MaybeC value = MaybeC.anyValue(); + for (Map> map : states) { + value.joinWith(map.get(sym)); + if (value.isNotConstant()) + break; // It cannot change once it is notConstant + } + result.put(sym, value); + } + return result; + } + + class VarVisitor extends AstVisitor> { + private final boolean primitive, reference; + + VarVisitor(boolean primitive, boolean reference) { + this.primitive = primitive; + this.reference = reference; + } + + @Override + public Void var(Var ast, Set arg) { + if (ast.sym.type.isReferenceType() && reference) + arg.add(ast.sym); + else if (!ast.sym.type.isReferenceType() && primitive) + arg.add(ast.sym); + return null; + } + } +} diff --git a/src/cd/transform/analysis/NullAnalysis.java b/src/cd/transform/analysis/NullAnalysis.java new file mode 100644 index 0000000..e119e1a --- /dev/null +++ b/src/cd/transform/analysis/NullAnalysis.java @@ -0,0 +1,59 @@ +package cd.transform.analysis; + +import cd.ir.Ast; +import cd.ir.Ast.Assign; +import cd.ir.Ast.NewArray; +import cd.ir.Ast.NewObject; +import cd.ir.ControlFlowGraph; +import cd.ir.ExprVisitor; +import cd.ir.Symbol.VariableSymbol; + +import java.util.Map; + +public class NullAnalysis extends MaybeFlowAnalysis { + public NullAnalysis(ControlFlowGraph cfg) { + super(cfg, false, true); + } + + @Override + protected MaybeC defaultInit(VariableSymbol varSym) { + if (varSym.kind == VariableSymbol.Kind.LOCAL) + return MaybeC.value(true); + else + return MaybeC.notConstant(); + } + + @Override + public void transferStmt(Ast.Stmt stmt, Map> state) { + if (stmt instanceof Assign) { + Assign assign = (Assign) stmt; + if (assign.left() instanceof Ast.Var && + state.containsKey(((Ast.Var) assign.left()).sym)) { + VariableSymbol sym = ((Ast.Var) assign.left()).sym; + if (!state.containsKey(sym)) return; + Boolean rightHandSide = new NullVisitor().visit(assign.right(), null); + if (rightHandSide != null) + state.put(sym, MaybeC.value(rightHandSide)); + else + state.get(sym).setNotConstant(); + } + } + } + + static class NullVisitor extends ExprVisitor { + @Override + public Boolean newObject(NewObject ast, Void arg) { + return false; + } + + @Override + public Boolean newArray(NewArray ast, Void arg) { + return false; + } + + @Override + public Boolean nullConst(Ast.NullConst ast, Void arg) { + return true; + } + } +} diff --git a/src/cd/transform/analysis/StaticMethodUsage.java b/src/cd/transform/analysis/StaticMethodUsage.java new file mode 100644 index 0000000..2671d5f --- /dev/null +++ b/src/cd/transform/analysis/StaticMethodUsage.java @@ -0,0 +1,39 @@ +package cd.transform.analysis; + +import cd.ir.Ast; +import cd.ir.Ast.ClassDecl; +import cd.ir.AstVisitor; +import cd.ir.BasicBlock; +import cd.ir.Symbol.ClassSymbol; +import cd.ir.Symbol.MethodSymbol; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class StaticMethodUsage extends AstVisitor> { + + public Set go(List classDecls) { + Set usedMethods = new HashSet<>(); + + for (ClassDecl classDecl : classDecls) { + if (classDecl.name.equals("Main")) { + Ast.MethodDecl mainMethod = classDecl.sym.getMethod("main").ast; + for (BasicBlock block : mainMethod.cfg.allBlocks) { + block.accept(this, usedMethods); + } + } + } + + return usedMethods; + } + + @Override + public Void methodCall(Ast.MethodCall ast, Set arg) { + ClassSymbol classSym = (ClassSymbol) ast.getMethodCallExpr().receiver().type; + MethodSymbol method = classSym.getMethod(ast.getMethodCallExpr().methodName); + arg.add(method); + visit(method.ast, arg); + return null; + } +} diff --git a/src/cd/transform/optimizers/BlockRemoval.java b/src/cd/transform/optimizers/BlockRemoval.java new file mode 100644 index 0000000..4e27221 --- /dev/null +++ b/src/cd/transform/optimizers/BlockRemoval.java @@ -0,0 +1,55 @@ +package cd.transform.optimizers; + +import cd.ir.Ast; +import cd.ir.BasicBlock; +import cd.ir.ControlFlowGraph; + +import java.util.ListIterator; + +public class BlockRemoval implements IOptimizer { + @Override + public boolean go(ControlFlowGraph cfg) { + boolean changed = false; + ListIterator iterator = cfg.allBlocks.listIterator(); + while (iterator.hasNext()) { + BasicBlock block = iterator.next(); + + if (block == cfg.start || block == cfg.end) + continue; + + if (block.condition == null + && (block.stmts.isEmpty() || (block.stmts.size() == 1 + && block.stmts.get(0) instanceof Ast.Nop))) { + // The block is empty or has a Nop, there is no branch + // Therefore the predecessors can be directly linked to the successor + BasicBlock successor = block.successors.get(0); + boolean cancelDeletion = false; + for (BasicBlock pred : block.predecessors) { + if (pred.condition != null) { + cancelDeletion = true; + break; + } + int posInPred = pred.successors.indexOf(block); + pred.successors.set(posInPred, successor); + } + if (cancelDeletion) continue; + for (BasicBlock succ : block.successors) { + succ.predecessors.remove(block); + succ.predecessors.addAll(block.predecessors); + } + iterator.remove(); + changed = true; + } else if (block.condition != null + && block.trueSuccessor() == block.falseSuccessor()) { + // There is a condition and both successors are equal, therefore we can remove it + BasicBlock successor = block.trueSuccessor(); + block.successors.clear(); + block.successors.add(successor); + block.condition = null; + changed = true; + } + } + + return changed; + } +} diff --git a/src/cd/transform/optimizers/BranchElimination.java b/src/cd/transform/optimizers/BranchElimination.java new file mode 100644 index 0000000..0f743a4 --- /dev/null +++ b/src/cd/transform/optimizers/BranchElimination.java @@ -0,0 +1,50 @@ +package cd.transform.optimizers; + +import cd.ir.BasicBlock; +import cd.ir.CompileTimeEvaluator; +import cd.ir.ControlFlowGraph; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import static cd.Config.TRUE; + +/** + * this class eliminates branches if a block condition evaluates to false at compile time + */ +public class BranchElimination implements IOptimizer { + @Override + public boolean go(ControlFlowGraph cfg) { + CompileTimeEvaluator cte = new CompileTimeEvaluator(); + Set removedSet = new HashSet<>(); + boolean changed = false; + + for (BasicBlock block : cfg.allBlocks) { + // Removed blocks and blocks without condition don't matter + if (block.condition == null || removedSet.contains(block)) + continue; + + // If the condition cannot be computed, skip block + Optional condition = cte.calc(block.condition); + if (!condition.isPresent()) continue; + + changed = true; + // Remove all connections to unreachable block + BasicBlock eliminated; + if (condition.get() == TRUE) + eliminated = block.falseSuccessor(); + else + eliminated = block.trueSuccessor(); + block.successors.remove(eliminated); + block.condition = null; + for (BasicBlock b : eliminated.successors) + b.predecessors.remove(eliminated); + removedSet.add(eliminated); + } + + // Can't edit the list while we are iterating over it + cfg.allBlocks.removeAll(removedSet); + return changed; + } +} diff --git a/src/cd/transform/optimizers/ConstantPropagator.java b/src/cd/transform/optimizers/ConstantPropagator.java new file mode 100644 index 0000000..542d5fe --- /dev/null +++ b/src/cd/transform/optimizers/ConstantPropagator.java @@ -0,0 +1,70 @@ +package cd.transform.optimizers; + +import cd.ir.Ast; +import cd.ir.Ast.BooleanConst; +import cd.ir.Ast.Expr; +import cd.ir.Ast.IntConst; +import cd.ir.Ast.Var; +import cd.ir.BasicBlock; +import cd.ir.CompileTimeEvaluator; +import cd.ir.ControlFlowGraph; +import cd.ir.Symbol.PrimitiveTypeSymbol; +import cd.ir.Symbol.VariableSymbol; +import cd.transform.analysis.ConstantAnalysis; + +import java.util.Map; +import java.util.Optional; + +import static cd.Config.TRUE; + +/** + * Class that propagates constant values to avoid accessing variables at runtime, + * and also includes simplifications to arithmetic operations. + */ +public class ConstantPropagator extends OptimizerVisitor> { + + private CompileTimeEvaluator cte = new CompileTimeEvaluator(); + + @Override + public boolean go(ControlFlowGraph cfg) { + setChanged(false); + + ConstantAnalysis constantAnalysis = new ConstantAnalysis(cfg); + for (BasicBlock block : cfg.allBlocks) { + block.accept(this, constantAnalysis.inStateConstantsOf(block)); + } + + return hasChanged(); + } + + @Override + public Ast assign(Ast.Assign ast, Map arg) { + // Visit and replace only the RHS of the statement + Expr lhs = ast.left(); + ast.setLeft(null); + visitChildren(ast, arg); + ast.setLeft(lhs); + + // Update the map (only with constant values) + if (ast.left() instanceof Var && !ast.left().type.isReferenceType()) { + Optional rightValue = cte.calc(ast.right(), arg); + if (rightValue.isPresent()) + arg.put(((Var) ast.left()).sym, rightValue.get()); + else + arg.remove(((Var) ast.left()).sym); + } + return ast; + } + + @Override + public Ast var(Var ast, Map arg) { + Integer value = arg.get(ast.sym); + if (value == null) + return ast; + if (ast.type == PrimitiveTypeSymbol.intType) + return new IntConst(value); + + assert ast.type == PrimitiveTypeSymbol.booleanType; + return new BooleanConst(value == TRUE); + } +} diff --git a/src/cd/transform/optimizers/GlobalOptimizerVisitor.java b/src/cd/transform/optimizers/GlobalOptimizerVisitor.java new file mode 100644 index 0000000..6227428 --- /dev/null +++ b/src/cd/transform/optimizers/GlobalOptimizerVisitor.java @@ -0,0 +1,23 @@ +package cd.transform.optimizers; + +import cd.ir.Ast; +import cd.ir.AstRewriteVisitor; + +public abstract class GlobalOptimizerVisitor
+ extends AstRewriteVisitor + implements IGlobalOptimizer { + private boolean changed; + + @Override + protected void nodeReplaced(Ast oldNode, Ast newNode) { + changed = true; + } + + boolean hasChanged() { + return changed; + } + + final void setChanged(boolean changed) { + this.changed = changed; + } +} diff --git a/src/cd/transform/optimizers/IGlobalOptimizer.java b/src/cd/transform/optimizers/IGlobalOptimizer.java new file mode 100644 index 0000000..e55d23b --- /dev/null +++ b/src/cd/transform/optimizers/IGlobalOptimizer.java @@ -0,0 +1,10 @@ +package cd.transform.optimizers; + +import cd.ir.Ast.ClassDecl; +import cd.ir.Symbol.TypeSymbol; + +import java.util.List; + +public interface IGlobalOptimizer { + boolean go(List classDecls, List typeList); +} diff --git a/src/cd/transform/optimizers/IOptimizer.java b/src/cd/transform/optimizers/IOptimizer.java new file mode 100644 index 0000000..b123300 --- /dev/null +++ b/src/cd/transform/optimizers/IOptimizer.java @@ -0,0 +1,7 @@ +package cd.transform.optimizers; + +import cd.ir.ControlFlowGraph; + +public interface IOptimizer { + boolean go(ControlFlowGraph cfg); +} diff --git a/src/cd/transform/optimizers/OptimizerVisitor.java b/src/cd/transform/optimizers/OptimizerVisitor.java new file mode 100644 index 0000000..12c79e5 --- /dev/null +++ b/src/cd/transform/optimizers/OptimizerVisitor.java @@ -0,0 +1,23 @@ +package cd.transform.optimizers; + +import cd.ir.Ast; +import cd.ir.AstRewriteVisitor; + +public abstract class OptimizerVisitor + extends AstRewriteVisitor implements IOptimizer { + private boolean changed; + + @Override + protected void nodeReplaced(Ast oldNode, Ast newNode) { + changed = true; + } + + boolean hasChanged() { + return changed; + } + + final void setChanged(boolean changed) { + this.changed = changed; + } +} + diff --git a/src/cd/transform/optimizers/TrivialOpRemoval.java b/src/cd/transform/optimizers/TrivialOpRemoval.java new file mode 100644 index 0000000..410ffc6 --- /dev/null +++ b/src/cd/transform/optimizers/TrivialOpRemoval.java @@ -0,0 +1,143 @@ +package cd.transform.optimizers; + +import cd.ir.Ast; +import cd.ir.Ast.*; +import cd.ir.BasicBlock; +import cd.ir.CompileTimeEvaluator; +import cd.ir.ControlFlowGraph; + +import java.util.Optional; + +import static cd.Config.TRUE; +import static cd.ir.Ast.UnaryOp.UOp.U_MINUS; + +public class TrivialOpRemoval extends OptimizerVisitor { + private CompileTimeEvaluator cte = new CompileTimeEvaluator(); + + @Override + public boolean go(ControlFlowGraph cfg) { + setChanged(false); + for (BasicBlock block : cfg.allBlocks) + block.accept(this, null); + return hasChanged(); + } + + @Override + public Ast binaryOp(BinaryOp ast, Void arg) { + visitChildren(ast, arg); + Optional rightRes = cte.calc(ast.right()); + Optional leftRes = cte.calc(ast.left()); + + if (leftRes.isPresent() && rightRes.isPresent()) { + // Skip the operation if both arguments' values are known + Optional resOpt = cte.calc(ast); + if (!resOpt.isPresent()) + return ast; + + switch (ast.operator) { + case B_TIMES: + case B_DIV: + case B_MOD: + case B_PLUS: + case B_MINUS: + return new IntConst(resOpt.get()); + default: + return new BooleanConst(resOpt.get() == TRUE); + } + + } else if (!leftRes.isPresent() && !rightRes.isPresent()) { + // Nothing can be done if no argument is known + return ast; + } + + switch (ast.operator) { + case B_PLUS: // a + 0 = a (commutative) + if (leftRes.isPresent() && leftRes.get() == 0) + return ast.right(); + else if (rightRes.isPresent() && rightRes.get() == 0) + return ast.left(); + break; + case B_MINUS: // a - 0 = a | 0 - a = -a + if (leftRes.isPresent() && leftRes.get() == 0) + return new UnaryOp(U_MINUS, ast.right()); + else if (rightRes.isPresent() && rightRes.get() == 0) + return ast.left(); + break; + case B_TIMES: // a * 0 = 0 | a * 1 = a | a * -1 = -a (commutative) + if (rightRes.isPresent()) { + if (rightRes.get() == 1) + return ast.left(); + else if (rightRes.get() == -1) + return new UnaryOp(U_MINUS, ast.left()); +// else if (rightRes.get() == 0) +// return new IntConst(0); + } else { + if (leftRes.get() == 1) + return ast.right(); + else if (leftRes.get() == -1) + return new UnaryOp(U_MINUS, ast.right()); +// else if (leftRes.get() == 0) +// return new IntConst(0); + } + break; + case B_DIV: // 0 / a = 0 | a / 1 = a | a / -1 = -a + if (rightRes.isPresent()) { + if (rightRes.get() == 1) + return ast.left(); + else if (rightRes.get() == -1) + return new UnaryOp(U_MINUS, ast.left()); +// } else if (leftRes.get() == 0) +// return new IntConst(0); + } + break; + case B_MOD: // 0 % a = 0 | a % 1 = 0 +// if (rightValid && rightValue == 1) +// return new IntConst(0); +// else if (leftValid && leftValue == 0) +// return new IntConst(0); + break; + } + return ast; + } + + @Override + public Ast unaryOp(UnaryOp ast, Void arg) { + visitChildren(ast, arg); + Optional valueOpt = cte.calc(ast); + + if (valueOpt.isPresent()) { + // Skip the operation if the argument's value is known + switch (ast.operator) { + case U_BOOL_NOT: + return new BooleanConst(valueOpt.get() == TRUE); + default: + return new IntConst(valueOpt.get()); + } + } else { + switch (ast.operator) { + case U_PLUS: + // The plus operator is meaningless + return ast.arg(); + default: + // Nested unary operators with the same operator are meaningless + if (ast.arg() instanceof UnaryOp) { + UnaryOp inner = (UnaryOp) ast.arg(); + if (inner.operator == ast.operator) + return inner.arg(); + else return ast; + } else { + return ast; + } + } + } + } + + + @Override + public Ast cast(Cast ast, Void arg) { + visitChildren(ast, arg); + if (ast.type == ast.arg().type) + return ast.arg(); + return ast; + } +} diff --git a/src/cd/transform/optimizers/UnusedClassRemoval.java b/src/cd/transform/optimizers/UnusedClassRemoval.java new file mode 100644 index 0000000..b1c6073 --- /dev/null +++ b/src/cd/transform/optimizers/UnusedClassRemoval.java @@ -0,0 +1,38 @@ +package cd.transform.optimizers; + +import cd.ir.Ast.ClassDecl; +import cd.ir.Symbol.ArrayTypeSymbol; +import cd.ir.Symbol.TypeSymbol; +import cd.transform.analysis.ClassUsage; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class UnusedClassRemoval extends GlobalOptimizerVisitor { + public boolean go(List classDecls, List typeList) { + Set used = new ClassUsage().go(classDecls); + Set toRemove = new HashSet<>(classDecls); + toRemove.removeAll(used); + + // Remove classes from list of declarations + classDecls.clear(); + classDecls.addAll(used); + + // Remove corresponding types and array types + for (ClassDecl classDecl : toRemove) { + typeList.remove(classDecl.sym); + TypeSymbol arrayType = null; + for (TypeSymbol type : typeList) { + if (type instanceof ArrayTypeSymbol && ((ArrayTypeSymbol) type).elementType == classDecl.sym) { + arrayType = type; + break; + } + } + assert arrayType != null; + typeList.remove(arrayType); + } + + return !toRemove.isEmpty(); + } +} diff --git a/src/cd/transform/optimizers/UnusedMethodRemoval.java b/src/cd/transform/optimizers/UnusedMethodRemoval.java new file mode 100644 index 0000000..049b64e --- /dev/null +++ b/src/cd/transform/optimizers/UnusedMethodRemoval.java @@ -0,0 +1,56 @@ +package cd.transform.optimizers; + +import cd.ir.Ast; +import cd.ir.Ast.ClassDecl; +import cd.ir.Ast.MethodDecl; +import cd.ir.Symbol.ClassSymbol; +import cd.ir.Symbol.MethodSymbol; +import cd.ir.Symbol.TypeSymbol; +import cd.transform.analysis.StaticMethodUsage; + +import java.util.List; +import java.util.Set; + +public class UnusedMethodRemoval extends GlobalOptimizerVisitor> { + + public boolean go(List classDecls, List typeList) { + Set used = new StaticMethodUsage().go(classDecls); + + for (ClassDecl classDecl : classDecls) + visit(classDecl, used); + + return hasChanged(); + } + + @Override + public Ast methodDecl(MethodDecl ast, Set arg) { + // To remove a method, it mustn't be in the Set + boolean keepMethod = arg.contains(ast.sym); + + if (keepMethod) return ast; + // But also needs not to be overriding a method in the Set + ClassSymbol classSym = ast.sym.owner; + while (classSym != ClassSymbol.objectType) { + keepMethod = arg.contains(classSym.getMethod(ast.name)); + classSym = classSym.superClass; + } + + if (keepMethod) return ast; + // Remove method from class (only Symbol, Visitor takes care of Ast) + ast.sym.owner.methods.remove(ast.name); + + // Remove all methods overridden by the deleted method (both Ast and Symbol, as we are not currently + // looping over it + classSym = ast.sym.owner; + while (classSym != ClassSymbol.objectType) { + assert classSym.ast != null; + classSym.ast.rwChildren.removeIf(child -> child instanceof MethodDecl && + ((MethodDecl) child).name.equals(ast.name)); + classSym.methods.remove(ast.name); + classSym = classSym.superClass; + } + + // Return from this classes' Ast node + return null; + } +} diff --git a/src/cd/transform/optimizers/WriteOnlyVarRemoval.java b/src/cd/transform/optimizers/WriteOnlyVarRemoval.java new file mode 100644 index 0000000..bf3f852 --- /dev/null +++ b/src/cd/transform/optimizers/WriteOnlyVarRemoval.java @@ -0,0 +1,58 @@ +package cd.transform.optimizers; + +import cd.ir.Ast; +import cd.ir.Ast.*; +import cd.ir.BasicBlock; +import cd.ir.ControlFlowGraph; +import cd.ir.ExprVisitor; +import cd.ir.Symbol.VariableSymbol; +import cd.transform.analysis.LiveVarAnalysis; + +import java.util.*; + +public class WriteOnlyVarRemoval extends OptimizerVisitor> { + @Override + public boolean go(ControlFlowGraph cfg) { + LiveVarAnalysis analysis = new LiveVarAnalysis(cfg); + for (BasicBlock block : cfg.allBlocks) { + Set state = new HashSet<>(analysis.outStateOf(block)); + if (block.condition != null) + new LiveVarAnalysis.Visitor().visit(block.condition, state); + for (ListIterator iterator = block.stmts.listIterator(block.stmts.size()); iterator.hasPrevious(); ) { + Stmt stmt = iterator.previous(); + Ast res = visit(stmt, state); + // TODO: this code should work? even if we iterate through the newly created Stmt nodes + if (stmt != res) { + iterator.remove(); + assert res instanceof Seq; + Seq seq = (Seq) res; + for (Ast ast : seq.rwChildren) { + assert ast instanceof Stmt; + iterator.add((Stmt) ast); + } + nodeReplaced(stmt, res); + } + } + } + return hasChanged(); + } + + @Override + public Ast assign(Assign ast, Set arg) { + if (ast.left() instanceof Var && !arg.contains(((Var) ast.left()).sym)) { + List methodCalls = new ArrayList<>(); + new MethodCallFinder().visit(ast.right(), methodCalls); + return new Seq(methodCalls); + } else { + return ast; + } + } + + private class MethodCallFinder extends ExprVisitor> { + @Override + public Void methodCall(Ast.MethodCallExpr ast, List arg) { + arg.add(new MethodCall(ast)); + return null; + } + } +} diff --git a/src/cd/util/DepthFirstSearchPreOrder.java b/src/cd/util/DepthFirstSearchPreOrder.java new file mode 100644 index 0000000..8c77874 --- /dev/null +++ b/src/cd/util/DepthFirstSearchPreOrder.java @@ -0,0 +1,60 @@ +package cd.util; + +import cd.ir.BasicBlock; +import cd.ir.ControlFlowGraph; + +import java.util.*; + +/** + * A potentially handy iterator which yields the blocks in a control-flow + * graph. The order is pre-order, depth-first. Pre-order means that a + * node is visited before its successors. + */ +public class DepthFirstSearchPreOrder implements Iterable { + + public final ControlFlowGraph cfg; + + public DepthFirstSearchPreOrder(ControlFlowGraph cfg) { + this.cfg = cfg; + } + + public Iterator iterator() { + return new Iterator() { + + /** Blocks we still need to visit */ + private final Stack stack = new Stack(); + + /** Blocks we pushed thus far */ + private final Set pushed = new HashSet(); + + { + stack.add(cfg.start); + pushed.add(cfg.start); + } + + public boolean hasNext() { + return !stack.isEmpty(); + } + + public BasicBlock next() { + if (stack.isEmpty()) + throw new NoSuchElementException(); + + BasicBlock res = stack.pop(); + for (BasicBlock s : res.successors) + if (!pushed.contains(s)) { + pushed.add(s); + stack.add(s); + } + + return res; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + +} diff --git a/src/cd/util/FileUtil.java b/src/cd/util/FileUtil.java index 991e33e..e543e3c 100644 --- a/src/cd/util/FileUtil.java +++ b/src/cd/util/FileUtil.java @@ -9,12 +9,12 @@ public class FileUtil { public static String readAll(Reader ubReader) throws IOException { try (BufferedReader bReader = new BufferedReader(ubReader)) { StringBuilder sb = new StringBuilder(); - + while (true) { int ch = bReader.read(); if (ch == -1) break; - + sb.append((char) ch); } return sb.toString(); @@ -32,7 +32,7 @@ public class FileUtil { } public static String runCommand(File dir, String[] command, - String[] substs, String input, boolean detectError) + String[] substs, String input, boolean detectError) throws IOException { // Substitute the substitution strings $0, $1, etc String newCommand[] = new String[command.length]; @@ -53,12 +53,12 @@ public class FileUtil { osw.write(input); } } - + try { final StringBuffer result = new StringBuffer(); // thread to read stdout from child so that p.waitFor() is interruptible // by JUnit's timeout mechanism. Otherwise it would block in readAll() - Thread t = new Thread () { + Thread t = new Thread() { public void run() { try { result.append(readAll(new InputStreamReader(p.getInputStream()))); @@ -67,7 +67,7 @@ public class FileUtil { } }; t.start(); - + if (detectError) { int err = p.waitFor(); @@ -76,7 +76,7 @@ public class FileUtil { if (err != 0) return "Error: " + err + "\n"; } - + t.join(); return result.toString(); } catch (InterruptedException e) { @@ -94,13 +94,15 @@ public class FileUtil { public static void findJavaliFiles(File testDir, List result) { for (File testFile : testDir.listFiles()) { if (testFile.getName().endsWith(".javali")) - result.add(new Object[] { testFile }); + result.add(new Object[]{testFile}); else if (testFile.isDirectory()) findJavaliFiles(testFile, result); } } - /** Finds all .javali under directory {@code testDir} and returns them. */ + /** + * Finds all .javali under directory {@code testDir} and returns them. + */ public static List findJavaliFiles(File testDir) { List result = new ArrayList(); for (File testFile : testDir.listFiles()) { diff --git a/src/cd/util/Pair.java b/src/cd/util/Pair.java index c11ee90..f3c9995 100644 --- a/src/cd/util/Pair.java +++ b/src/cd/util/Pair.java @@ -3,16 +3,18 @@ package cd.util; import java.util.ArrayList; import java.util.List; -/** Simple class for joining two objects of the same type */ +/** + * Simple class for joining two objects of the same type + */ public class Pair { public T a; public T b; - + public Pair(T a, T b) { this.a = a; this.b = b; } - + public static List> zip(List listA, List listB) { List> res = new ArrayList>(); for (int i = 0; i < Math.min(listA.size(), listB.size()); i++) { @@ -20,24 +22,24 @@ public class Pair { } return res; } - + public static List unzipA(List> list) { List res = new ArrayList(); for (Pair p : list) res.add(p.a); return res; } - + public static List unzipB(List> list) { List res = new ArrayList(); for (Pair p : list) res.add(p.b); return res; } - + public static String join( - List> pairs, - String itemSep, + List> pairs, + String itemSep, String pairSep) { StringBuilder sb = new StringBuilder(); boolean first = true; diff --git a/src/cd/util/Tuple.java b/src/cd/util/Tuple.java index fd377b4..96710a3 100644 --- a/src/cd/util/Tuple.java +++ b/src/cd/util/Tuple.java @@ -1,15 +1,17 @@ package cd.util; -/** Simple class for joining two objects of different type */ -public class Tuple { +/** + * Simple class for joining two objects of different type + */ +public class Tuple { public A a; public B b; - + public Tuple(A a, B b) { this.a = a; this.b = b; } - + public String toString() { return "(" + a.toString() + ", " + b.toString() + ")"; } diff --git a/src/cd/util/debug/AstDump.java b/src/cd/util/debug/AstDump.java index cd7a975..71fc8ac 100644 --- a/src/cd/util/debug/AstDump.java +++ b/src/cd/util/debug/AstDump.java @@ -8,11 +8,11 @@ import java.lang.reflect.Field; import java.util.*; public class AstDump { - + public static String toString(Ast ast) { return toString(ast, ""); } - + public static String toString(List astRoots) { StringBuilder sb = new StringBuilder(); for (Ast a : astRoots) { @@ -26,22 +26,22 @@ public class AstDump { ad.dump(ast, indent); return ad.sb.toString(); } - + public static String toStringFlat(Ast ast) { AstDump ad = new AstDump(); ad.dumpFlat(ast); return ad.sb.toString(); } - + private StringBuilder sb = new StringBuilder(); private Visitor vis = new Visitor(); - + protected void dump(Ast ast, String indent) { // print out the overall class structure sb.append(indent); String nodeName = ast.getClass().getSimpleName(); List> flds = vis.visit(ast, null); - sb.append(String.format("%s (%s)\n", + sb.append(String.format("%s (%s)\n", nodeName, Pair.join(flds, ": ", ", "))); @@ -51,11 +51,11 @@ public class AstDump { dump(child, newIndent); } } - + protected void dumpFlat(Ast ast) { String nodeName = ast.getClass().getSimpleName(); List> flds = vis.visit(ast, null); - sb.append(String.format("%s(%s)[", + sb.append(String.format("%s(%s)[", nodeName, Pair.join(flds, ":", ","))); @@ -68,39 +68,39 @@ public class AstDump { sb.append("]"); } - + protected class Visitor extends AstVisitor>, Void> { - + @Override protected List> dflt(Ast ast, Void arg) { ArrayList> res = new ArrayList>(); - + // Get the list of fields and sort them by name: java.lang.Class rclass = ast.getClass(); - List rflds = - Arrays.asList(rclass.getFields()); - Collections.sort(rflds, new Comparator () { + List rflds = + Arrays.asList(rclass.getFields()); + Collections.sort(rflds, new Comparator() { public int compare(Field o1, Field o2) { return o1.getName().compareTo(o2.getName()); } }); - + // Create pairs for each one that is not of type Ast: for (java.lang.reflect.Field rfld : rflds) { rfld.setAccessible(true); - + // ignore various weird fields that show up from // time to time: if (rfld.getName().startsWith("$")) continue; - + // ignore fields of AST type, and rwChildren (they should be // uncovered using the normal tree walk) if (rfld.getType().isAssignableFrom(Ast.class)) continue; if (rfld.getName().equals("rwChildren")) continue; - + // ignore NULL fields, but add others to our list of pairs try { Object value = rfld.get(ast); @@ -110,12 +110,12 @@ public class AstDump { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); - } + } } - + return res; } - + } } diff --git a/src/cd/util/debug/AstOneLine.java b/src/cd/util/debug/AstOneLine.java index 9d64dbd..874e54f 100644 --- a/src/cd/util/debug/AstOneLine.java +++ b/src/cd/util/debug/AstOneLine.java @@ -5,13 +5,13 @@ import cd.ir.Ast.*; import cd.ir.AstVisitor; public class AstOneLine { - + public static String toString(Ast ast) { return new Visitor().visit(ast, null); } protected static class Visitor extends AstVisitor { - + public String str(Ast ast) { return ast.accept(this, null); } @@ -23,7 +23,7 @@ public class AstOneLine { @Override public String binaryOp(BinaryOp ast, Void arg) { - return String.format("(%s %s %s)", + return String.format("(%s %s %s)", str(ast.left()), ast.operator.repr, str(ast.right())); } @@ -36,12 +36,12 @@ public class AstOneLine { public String builtInRead(BuiltInRead ast, Void arg) { return String.format("read()"); } - + @Override public String builtInWrite(BuiltInWrite ast, Void arg) { return String.format("write(%s)", str(ast.arg())); } - + @Override public String builtInWriteln(BuiltInWriteln ast, Void arg) { return String.format("writeln()"); @@ -81,12 +81,12 @@ public class AstOneLine { public String methodCall(MethodCall ast, Void arg) { return str(ast.getMethodCallExpr()); } - + @Override public String methodCall(MethodCallExpr ast, Void arg) { return String.format("%s.%s(...)", str(ast.receiver()), ast.methodName); } - + @Override public String methodDecl(MethodDecl ast, Void arg) { return String.format("%s %s(...) {...}", ast.returnType, ast.name); @@ -121,7 +121,7 @@ public class AstOneLine { public String thisRef(ThisRef ast, Void arg) { return "this"; } - + @Override public String returnStmt(ReturnStmt ast, Void arg) { return ast.arg() != null ? String.format("return %s", str(ast.arg())) : "return"; @@ -136,10 +136,10 @@ public class AstOneLine { public String var(Var ast, Void arg) { { if (ast.sym != null) { - String symName = ast.sym.toString(); + String symName = ast.sym.toString(); if (ast.name == null || ast.name.equals(symName)) return symName; - + // Return something strange to warn about the mismatch here: return String.format("(%s!=%s)", symName, ast.name); } else @@ -156,6 +156,6 @@ public class AstOneLine { public String whileLoop(WhileLoop ast, Void arg) { return String.format("while (%s) {...}", str(ast.condition())); } - + } } diff --git a/src/cd/util/debug/CfgDump.java b/src/cd/util/debug/CfgDump.java new file mode 100644 index 0000000..ed646ed --- /dev/null +++ b/src/cd/util/debug/CfgDump.java @@ -0,0 +1,156 @@ +package cd.util.debug; + +import cd.ir.Ast.ClassDecl; +import cd.ir.Ast.MethodDecl; +import cd.ir.Ast.Stmt; +import cd.ir.BasicBlock; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; + +public class CfgDump { + + public static void toString( + MethodDecl mdecl, + String phase, + File filename, + boolean dumpDominators) { + if (filename == null) return; + FileWriter fw; + try { + fw = new FileWriter(new File(filename.getAbsolutePath() + phase + ".dot")); + fw.write(toString(mdecl, dumpDominators)); + fw.close(); + } catch (IOException e) { + } + } + + public static void toString( + List astRoots, + String phase, + File filename, + boolean dumpDominators) { + if (filename == null) return; + FileWriter fw; + try { + fw = new FileWriter(new File(filename.getAbsolutePath() + phase + ".dot")); + fw.write(toString(astRoots, dumpDominators)); + fw.close(); + } catch (IOException e) { + } + } + + public static String toString( + List astRoots, + boolean dumpDominators) { + return new CfgDump().dump(astRoots, dumpDominators); + } + + public static String toString( + MethodDecl mdecl, + boolean dumpDominators) { + return new CfgDump().dump(mdecl, dumpDominators); + } + + private int indent = 0; + private final StringBuilder sb = new StringBuilder(); + + private void append(String format, Object... args) { + String out = String.format(format, args); + if (out.startsWith("}") || out.startsWith("]")) indent -= 2; + for (int i = 0; i < indent; i++) sb.append(" "); + if (out.endsWith("{") || out.endsWith("[")) indent += 2; + sb.append(String.format(format, args)); + sb.append("\n"); + } + + private String dump( + MethodDecl mdecl, + boolean dumpDominators) { + sb.setLength(0); + append("digraph G {"); + append("graph [ rankdir = \"LR\" ];"); + + dumpBlocks(dumpDominators, mdecl, ""); + + append("}"); + + return sb.toString(); + } + + private String dump( + List astRoots, + boolean dumpDominators) { + sb.setLength(0); + append("digraph G {"); + append("graph [ rankdir = \"LR\" ];"); + + int sgcntr = 0, mcntr = 0; + for (ClassDecl cdecl : astRoots) { + //append("subgraph cluster_%d {", sgcntr++); + //append("label = \"%s\";", cdecl.name); + + for (MethodDecl mdecl : cdecl.methods()) { + append("subgraph cluster_%d {", sgcntr++); + append("label = \"%s.%s\"", cdecl.name, mdecl.name); + String m = String.format("M%d_", mcntr++); + + dumpBlocks(dumpDominators, mdecl, m); + + append("}"); + } + + //append("}"); + } + + append("}"); + + return sb.toString(); + } + + private void dumpBlocks(boolean dumpDominators, MethodDecl mdecl, String m) { + for (BasicBlock blk : mdecl.cfg.allBlocks) { + append("%sBB%d [", m, blk.index); + append("shape=\"record\""); + + // If we are not just dumping dominators, then build up + // a label with all the instructions in the block. + StringBuilder blklbl = new StringBuilder(); + blklbl.append(String.format("BB%d", blk.index)); + if (!dumpDominators || true) { + for (Stmt stmt : blk.stmts) + blklbl.append("|").append(AstOneLine.toString(stmt)); + if (blk.condition != null) + blklbl.append("|If: " + AstOneLine.toString(blk.condition)); + } + String[] replacements = new String[]{ + "<", "\\<", + ">", "\\>", + "@", "\\@", + "||", "\\|\\|", + }; + String blklbls = blklbl.toString(); + for (int i = 0; i < replacements.length; i += 2) + blklbls = blklbls.replace(replacements[i], replacements[i + 1]); + append("label=\"%s\"", blklbls); + append("];"); + + for (int idx = 0; idx < blk.successors.size(); idx++) { + BasicBlock sblk = blk.successors.get(idx); + String edgelbl = (idx == 0 ? "" : " [label=\"False\"]"); + append("%sBB%d -> %sBB%d%s;", + m, blk.index, m, sblk.index, edgelbl); + } + + { + if (dumpDominators && blk.dominatorTreeParent != null) { + append("%sBB%d -> %sBB%d [color=\"red\" label=\"dom\"];", + m, blk.index, m, blk.dominatorTreeParent.index); + } + } + } + } + +} diff --git a/src/cd/util/debug/DumpUtils.java b/src/cd/util/debug/DumpUtils.java index a78e8bd..e09fd3e 100644 --- a/src/cd/util/debug/DumpUtils.java +++ b/src/cd/util/debug/DumpUtils.java @@ -11,13 +11,13 @@ import java.util.Set; import static java.util.Collections.sort; class DumpUtils { - + static final Comparator classComparator = new Comparator() { public int compare(ClassDecl left, ClassDecl right) { return left.name.compareTo(right.name); } }; - + static final Comparator methodComparator = new Comparator() { public int compare(MethodDecl left, MethodDecl right) { return left.name.compareTo(right.name); @@ -26,7 +26,7 @@ class DumpUtils { static List sortedStrings(Set set) { List strings = new ArrayList(); - for(Object element : set) + for (Object element : set) strings.add(element.toString()); sort(strings); return strings; diff --git a/test/cd/AbstractTestAgainstFrozenReference.java b/test/cd/AbstractTestAgainstFrozenReference.java index dfbe35a..e1f9198 100644 --- a/test/cd/AbstractTestAgainstFrozenReference.java +++ b/test/cd/AbstractTestAgainstFrozenReference.java @@ -61,6 +61,9 @@ abstract public class AbstractTestAgainstFrozenReference { { if (passedSemanticAnalysis) { testCodeGenerator(astRoots); + + { + } } } } @@ -108,7 +111,7 @@ abstract public class AbstractTestAgainstFrozenReference { private static String referenceVersion() { { - return "CD_HW_CODEGEN_FULL_SOL"; + return "BENCH"; } } @@ -194,7 +197,7 @@ abstract public class AbstractTestAgainstFrozenReference { return res; } } - + /** * Run the code generator, assemble the resulting .s file, and (if the output * is well-defined) compare against the expected output. diff --git a/test/cd/BenchmarksRunner.java b/test/cd/BenchmarksRunner.java new file mode 100644 index 0000000..6ad4dc5 --- /dev/null +++ b/test/cd/BenchmarksRunner.java @@ -0,0 +1,345 @@ +package cd; + +import static java.nio.file.Files.lines; +import static java.nio.file.Files.write; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.ProcessBuilder.Redirect; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import cd.backend.codegen.CfgCodeGenerator; +import cd.backend.interpreter.DynamicError; +import cd.backend.interpreter.Interpreter; +import cd.backend.interpreter.StaticError; +import cd.frontend.parser.ParseFailure; +import cd.frontend.semantic.SemanticFailure; +import cd.ir.Ast.ClassDecl; +import cd.util.FileUtil; +import cd.util.Pair; + +public class BenchmarksRunner { + + // MAIN_OVERHEAD is an estimate of instructions executed by an empty 'main' in Javali. + private static final long MAIN_OVERHEAD = 150000; + + public static final File BENCH_DIR = new File("benchmarks"); + //public static final File BENCH_DIR = new File("javali_tests"); + + public static void main(String[] args) { + BenchmarksRunner runner = new BenchmarksRunner(); + runner.runBenchmarks(); + } + + private final Collection benchmarks; + private File sFile; + private File binFile; + private File inFile; + private Main main; + private File optRefFile; + private File execRefFile; + private File sRefFile; + private File binRefFile; + + public BenchmarksRunner() { + benchmarks = collectBenchs(); + } + + /** + * Return a list of all Javali-files in a directory (recursively) + */ + private static Collection collectBenchs() { + List result = new ArrayList<>(); + for (File file : FileUtil.findJavaliFiles(BENCH_DIR)) + result.add(file); + + return result; + } + + /** + * Run all benchmarks found in BENCH_DIR and compare with the reference compiler. + */ + public void runBenchmarks() { + System.out.println("Benchmark\tAST reduction\tValgrind reduction"); + for (File file : benchmarks) { + this.sFile = new File(file.getPath() + Config.ASMEXT); + this.binFile = new File(file.getPath() + Config.BINARYEXT); + this.inFile = new File(file.getPath() + ".in"); + this.execRefFile = new File(file.getPath() + ".exec.ref"); + this.optRefFile = new File(file.getPath() + ".opt.ref"); + this.sRefFile = new File(file.getPath() + ".ref" + Config.ASMEXT); + this.binRefFile = new File(file.getPath() + ".ref" + Config.BINARYEXT); + + System.out.print(file.getName() + "\t"); + try { + Pair improvement = runOneBench(file); + System.out.printf("%.1f%%\t%.1f%%\n", + improvement.a * 100, improvement.b * 100); + } catch (BenchmarkError e) { + System.out.println(e.getMessage()); + } catch (Throwable t) { + System.out.println("ERROR"); + } + } + } + + /** + * Run a Javali-program on the reference compiler + */ + private void runReference(File file) throws IOException, InterruptedException { + String slash = File.separator; + String colon = File.pathSeparator; + String javaExe = System.getProperty("java.home") + slash + "bin" + slash + Config.JAVA_EXE; + + String benchV = "BENCH2"; + + ProcessBuilder pb = new ProcessBuilder(javaExe, + "-Dcd.meta_hidden.Version="+benchV, "-cp", + "lib/frozenReferenceObf.jar" + colon + " lib/junit-4.12.jar" + colon + + "lib/antlr-4.7.1-complete.jar", + "cd.FrozenReferenceMain", file.getAbsolutePath()); + + pb.redirectOutput(Redirect.INHERIT); + pb.redirectError(Redirect.INHERIT); + + Process proc = pb.start(); + proc.waitFor(); + try (InputStream err = proc.getErrorStream()) { + if (err.available() > 0) { + byte b[] = new byte[err.available()]; + err.read(b, 0, b.length); + System.err.println(new String(b)); + } + } + } + + /** + * Run and compare one javali-benchmark. Returns two "improvement" numbers that + * represent the reduction in instructions executed. (once on the interpreter, once + * as compiled code using valgrind) + */ + private Pair runOneBench(File file) throws BenchmarkError { + // Delete intermediate files from previous runs: + if (sFile.exists()) + sFile.delete(); + if (binFile.exists()) + binFile.delete(); + + // Read test-input file + String input = ""; + try { + if (inFile.exists()) + input = FileUtil.read(inFile); + } catch (IOException e1) { + throw new BenchmarkError("Error while reading .in file"); + } + + // Run test on reference implementation + String execRef, optRef; + try { + runReference(file); + execRef = FileUtil.read(execRefFile); + optRef = FileUtil.read(optRefFile); + } catch (IOException | InterruptedException e) { + System.err.println(e); + throw new BenchmarkError("Error while running on reference"); + } + + // Call frontend of compiler-under-test (CUT) + List astRoots; + PrintStream oldStdOut = System.out; + try { + System.setOut(new PrintStream(new File("stdout-log.txt"))); + + this.main = new Main(); + this.main.debug = new StringWriter(); + + try { + astRoots = main.parse(new FileReader(file)); + } catch (ParseFailure | IOException pf) { + System.err.println(pf); + throw new BenchmarkError("ParseFailure"); + } + + try { + main.semanticCheck(astRoots); + } catch (SemanticFailure sf) { + System.err.println(sf); + throw new BenchmarkError("SemanticFailure"); + } + } catch (FileNotFoundException e) { + System.err.println(e); + throw new BenchmarkError("Failed to redirect stdout"); + } finally { + System.setOut(oldStdOut); + } + + // Run IR of compiled test with interpreter to count + // instructions + final StringWriter outputWriter = new StringWriter(); + final Reader inputReader = new StringReader(input); + final Interpreter interp = new Interpreter(astRoots, inputReader, outputWriter); + + try { + interp.execute(); + } catch (StaticError | DynamicError err) { + System.err.println(err); + throw new BenchmarkError("Error while running the interpreter"); + } + + // If output is the same, calculate the reduction in + // instructions by the CUT compared to the reference + // compiler + String execOut = outputWriter.toString(); + if (!execOut.equals(execRef)) + throw new BenchmarkError("Output is incorrect"); + + String optOut = interp.operationSummary(); + double refCount = getTotalOps(optRef); + double outCount = getTotalOps(optOut); + + double interpReduction = 0; + if (refCount != 0 || outCount != 0) + interpReduction = 1 - outCount / refCount; + + double valgrindReduction = 0; + // on BENCH2, we also compare the generated assembly using valgrind + valgrindReduction = compareWithValgrind(astRoots, input); + + return new Pair<>(interpReduction, valgrindReduction); + } + + + /** + * Generate assembly, assemble, and run the executables of the reference + * and the CUT on valgrind to get a deterministic instruction count. Return + * the reduction of instructions compared to the reference on BENCH2. + */ + private double compareWithValgrind(List astRoots, String input) throws BenchmarkError { + // generate ASM + try (FileWriter fout = new FileWriter(sFile)) { + CfgCodeGenerator cg = new CfgCodeGenerator(main, fout); + cg.go(astRoots); + } catch (IOException e) { + System.err.println(e); + throw new BenchmarkError("IOException when generating ASM"); + } + + // assemble the generated and the reference's assembly file + try { + FileUtil.runCommand( + Config.ASM_DIR, Config.ASM, + new String[] { binFile.getAbsolutePath(), sFile.getAbsolutePath() }, + null, false); + if (!binFile.exists()) + throw new BenchmarkError("Error while assembling ASM"); + } catch (IOException e) { + System.err.println(e); + throw new BenchmarkError("Error while assembling ASM"); + } + + try { + FileUtil.runCommand( + Config.ASM_DIR, Config.ASM, + new String[] { binRefFile.getAbsolutePath(), sRefFile.getAbsolutePath() }, + null, false); + if (!binRefFile.exists()) + throw new BenchmarkError("Error while assembling ASM from reference"); + } catch (IOException e) { + System.err.println(e); + throw new BenchmarkError("Error while assembling ASM from reference"); + } + + // run both binaries on valgrind + String execOut; + try { + String[] cmd = {"valgrind", "--tool=callgrind", binFile.getAbsolutePath()}; + execOut = FileUtil.runCommand(new File("."), + cmd, new String[] {}, + input, true); + } catch (IOException e) { + System.err.println(e); + throw new BenchmarkError("Error while running binary"); + } + + String execRefOut; + try { + String[] cmd = {"valgrind", "--tool=callgrind", binRefFile.getAbsolutePath()}; + execRefOut = FileUtil.runCommand(new File("."), + cmd, new String[] {}, + input, true); + } catch (IOException e) { + System.err.println(e); + throw new BenchmarkError("Error while running binary from reference"); + } + + // parse output of valgrind and return reduction in instructions + double instrCount = parseInstrCount(execOut) - MAIN_OVERHEAD; + double instrRefCount = parseInstrCount(execRefOut) - MAIN_OVERHEAD; + + double valgrindReduction = 0; + if (instrCount != 0 || instrRefCount != 0) + valgrindReduction = 1 - instrCount / instrRefCount; + + return valgrindReduction; + } + + /** + * Parse output of valgrind and return total instruction count + */ + private long parseInstrCount(String execOut_) throws BenchmarkError { + String execOut = execOut_.replace("\r\n", "\n"); + for (String line : execOut.split("\n")) { + if (line.contains(" I refs:")) { + String[] args = line.split(": "); + String number = args[1].trim(); + number = number.replace(",", ""); + return Long.valueOf(number); + } + } + + throw new BenchmarkError("Error while parsing valgrind output"); + } + + /** + * Parse operation summary of interpreter and get the total count of instructions executed + * (Binary+Unary ops) + */ + private int getTotalOps(String optCount_) { + String optCount = optCount_.replace("\r\n", "\n"); + int sum = 0; + for (String line : optCount.split("\n")) { + if (!line.equals("")) { + String[] args = line.split(": "); + sum += Integer.valueOf(args[1]); + } + } + + return sum; + } + + class BenchmarkError extends Exception { + private static final long serialVersionUID = 1L; + + public BenchmarkError(String arg) { + super(arg); + } + } +} diff --git a/test/cd/TestSamplePrograms.java b/test/cd/TestSamplePrograms.java index 7d53fd5..0d8c471 100644 --- a/test/cd/TestSamplePrograms.java +++ b/test/cd/TestSamplePrograms.java @@ -69,6 +69,8 @@ public class TestSamplePrograms extends AbstractTestAgainstFrozenReference { this.main = new Main(); this.main.debug = new StringWriter(); + this.main.cfgdumpbase = file; + } } diff --git a/test/cd/backend/interpreter/DynamicError.java b/test/cd/backend/interpreter/DynamicError.java new file mode 100644 index 0000000..224caac --- /dev/null +++ b/test/cd/backend/interpreter/DynamicError.java @@ -0,0 +1,32 @@ +package cd.backend.interpreter; + +import cd.backend.ExitCode; +import cd.util.FileUtil; + +/** Thrown in cases that are not ruled out by static analysis: */ +@SuppressWarnings("serial") +public class DynamicError extends RuntimeException { + private ExitCode code; + + public DynamicError(String message, ExitCode code) { + super(message); + this.code = code; + } + + public DynamicError(Throwable thr, ExitCode code) { + super(thr); + this.code = code; + } + + public ExitCode getExitCode() { + return this.code; + } + /** + * Returns a string exactly like the one that + * {@link FileUtil#runCommand(java.io.File, String[], String[], String, boolean)} + * returns when a command results in an error. */ + public String format() { + return "Error: " + code.value + "\n"; + } + +} \ No newline at end of file diff --git a/test/cd/backend/interpreter/Interpreter.java b/test/cd/backend/interpreter/Interpreter.java new file mode 100644 index 0000000..2d89a8a --- /dev/null +++ b/test/cd/backend/interpreter/Interpreter.java @@ -0,0 +1,585 @@ +package cd.backend.interpreter; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.InputMismatchException; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Scanner; + +import cd.backend.ExitCode; +import cd.backend.interpreter.Interpreter.MethodInterp.EarlyReturnException; +import cd.ir.Ast; +import cd.ir.Ast.Assign; +import cd.ir.Ast.BinaryOp; +import cd.ir.Ast.BooleanConst; +import cd.ir.Ast.BuiltInRead; +import cd.ir.Ast.BuiltInWrite; +import cd.ir.Ast.BuiltInWriteln; +import cd.ir.Ast.Cast; +import cd.ir.Ast.ClassDecl; +import cd.ir.Ast.Expr; +import cd.ir.Ast.Field; +import cd.ir.Ast.IfElse; +import cd.ir.Ast.Index; +import cd.ir.Ast.IntConst; +import cd.ir.Ast.MethodCall; +import cd.ir.Ast.MethodCallExpr; +import cd.ir.Ast.MethodDecl; +import cd.ir.Ast.NewArray; +import cd.ir.Ast.NewObject; +import cd.ir.Ast.NullConst; +import cd.ir.Ast.ReturnStmt; +import cd.ir.Ast.Stmt; +import cd.ir.Ast.ThisRef; +import cd.ir.Ast.UnaryOp; +import cd.ir.Ast.Var; +import cd.ir.Ast.WhileLoop; +import cd.ir.AstVisitor; +import cd.ir.BasicBlock; +import cd.ir.ControlFlowGraph; +import cd.ir.Symbol.ArrayTypeSymbol; +import cd.ir.Symbol.ClassSymbol; +import cd.ir.Symbol.TypeSymbol; +import cd.ir.Symbol.VariableSymbol; + +/** + * An interpreter for the Javali IR. It requires that the IR be fully + * semantically analyzed -- in particular, that symbols be assigned to Var and + * Field nodes. + * + * It can interpret either the AST used in CD1 or the CFG from CD2. It detects + * infinite loops and also tracks how many operations of each kind were + * performed. + */ +public class Interpreter { + + private static final long MAX_STEPS = 100000000; + + private long steps = 0; + private final JlNull nullPointer = new JlNull(); + + private final List classDecls; + private final Writer output; + private final Scanner input; + + public Interpreter(List classDecls, Reader in, Writer out) { + this.classDecls = classDecls; + this.input = new Scanner(in); + this.output = out; + } + + public void execute() { + ClassSymbol mainType = findMainClass(); + invokeMethod(mainType, "main", new JlObject(mainType), + Collections. emptyList()); + } + + // Optimization detection: + // + // We count the number of binary and unary operations that + // occurred during execution and compare this to a fully-optimized version. + // The key to this hashtable is either a BinaryOp.BOp or UnaryOp.UOp. + private final Map opCounts = new HashMap(); + + private void increment(Object operator) { + Integer current = opCounts.get(operator); + if (current == null) + opCounts.put(operator, 1); + else + opCounts.put(operator, current + 1); + } + + public String operationSummary() { + + List operationSummaries = new ArrayList(); + + for (Object operation : opCounts.keySet()) { + operationSummaries.add(String.format("%s: %s\n", operation, + opCounts.get(operation))); + } + + Collections.sort(operationSummaries); + + StringBuilder sb = new StringBuilder(); + for (String summary : operationSummaries) { + sb.append(summary); + } + + return sb.toString(); + } + + public void step() { + + // Stop after taking too many evaluation steps! + if (++steps > MAX_STEPS) { + throw new DynamicError("Infinite Loop!", + ExitCode.INFINITE_LOOP); + } + + } + + // The interpreter proper: + class ExprInterp extends AstVisitor { + + private JlValue v(int value) { + return new JlInt(value); + } + + private JlValue v(boolean b) { + return new JlBoolean(b); + } + + @Override + public JlValue visit(Ast ast, StackFrame arg) { + step(); + return super.visit(ast, arg); + } + + @Override + public JlValue binaryOp(BinaryOp ast, StackFrame arg) { + + try { + + final JlValue left = visit(ast.left(), arg); + final JlValue right = visit(ast.right(), arg); + + // TODO Only increment this operator for integers + increment(ast.operator); + switch (ast.operator) { + case B_TIMES : + return left.times(right); + case B_DIV : + return left.div(right); + case B_MOD : + return left.mod(right); + case B_PLUS : + return left.add(right); + case B_MINUS : + return left.subtract(right); + case B_AND : + return left.and(right); + case B_OR : + return left.or(right); + case B_EQUAL : + return v(left.equals(right)); + case B_NOT_EQUAL : + return v(!left.equals(right)); + case B_LESS_THAN : + return left.less(right); + case B_LESS_OR_EQUAL : + return left.lessOrEqual(right); + case B_GREATER_THAN : + return left.greater(right); + case B_GREATER_OR_EQUAL : + return left.greaterOrEqual(right); + } + + throw new DynamicError("Unhandled binary operator", + ExitCode.INTERNAL_ERROR); + + } catch (ArithmeticException e) { + throw new DynamicError("Division by zero", + ExitCode.DIVISION_BY_ZERO); + } + } + + @Override + public JlValue booleanConst(BooleanConst ast, StackFrame arg) { + return v(ast.value); + } + + @Override + public JlValue builtInRead(BuiltInRead ast, StackFrame arg) { + try { + return v(input.nextInt()); + } catch (InputMismatchException e) { + throw new DynamicError("Your .javali.in file is malformed.", + ExitCode.INTERNAL_ERROR); + } catch (NoSuchElementException e) { + throw new DynamicError("Your .javali.in does not contain enough numbers" + + " or may not exist at all.\nMake sure that test cases that contain " + + "read() expressions provide a [testcasename].javali.in file.", + ExitCode.INTERNAL_ERROR); + } + } + + @Override + public JlValue cast(Cast ast, StackFrame arg) { + + JlReference ref = visit(ast.arg(), arg).asRef(); + + if (ref.canBeCastTo(ast.typeName)) { + return ref; + } + + throw new DynamicError("Cast failure: cannot cast " + ref.typeSym + + " to " + ast.typeName, ExitCode.INVALID_DOWNCAST); + + } + + @Override + public JlValue field(Field ast, StackFrame arg) { + JlValue lhs = visit(ast.arg(), arg); + return lhs.asRef().field(ast.sym); + } + + @Override + public JlValue index(Index ast, StackFrame arg) { + JlValue lhs = visit(ast.left(), arg); + JlValue idx = visit(ast.right(), arg); + return lhs.asRef().deref(idx.asInt()); + } + + @Override + public JlValue intConst(IntConst ast, StackFrame arg) { + return v(ast.value); + } + + @Override + public JlValue newArray(NewArray ast, StackFrame arg) { + JlValue size = visit(ast.arg(), arg); + return new JlArray((ArrayTypeSymbol) ast.type, size.asInt()); + } + + @Override + public JlValue newObject(NewObject ast, StackFrame arg) { + return new JlObject((ClassSymbol) ast.type); + } + + @Override + public JlValue nullConst(NullConst ast, StackFrame arg) { + return nullPointer; + } + + @Override + public JlValue thisRef(ThisRef ast, StackFrame arg) { + return arg.getThisPointer(); + } + + @Override + public JlValue methodCall(MethodCallExpr ast, StackFrame frame) { + + JlObject rcvr = expr(ast.receiver(), frame).asObject(); + + List arguments = new ArrayList(); + for (Ast arg : ast.argumentsWithoutReceiver()) { + arguments.add(expr(arg, frame)); + } + + return invokeMethod((ClassSymbol) rcvr.typeSym, ast.methodName, + rcvr, arguments); + + } + + @Override + public JlValue unaryOp(UnaryOp ast, StackFrame arg) { + + JlValue val = visit(ast.arg(), arg); + + // TODO Increment this only when is an int + increment(ast.operator); + + switch (ast.operator) { + case U_PLUS : + return val.plus(); + case U_MINUS : + return val.minus(); + case U_BOOL_NOT : + return val.not(); + } + + throw new DynamicError("Unhandled unary operator " + ast.operator, + ExitCode.INTERNAL_ERROR); + } + + @Override + public JlValue var(Var ast, StackFrame arg) { + + if (ast.sym == null) { + throw new DynamicError("Var node with null symbol", + ExitCode.INTERNAL_ERROR); + } + + switch (ast.sym.kind) { + + case LOCAL : + case PARAM : + return arg.var(ast.sym); + case FIELD : + return arg.getThisPointer().field(ast.sym); + } + + throw new DynamicError("Unhandled VariableSymbol kind: " + + ast.sym.kind, ExitCode.INTERNAL_ERROR); + + } + + } + + public JlValue expr(Ast ast, StackFrame frame) { + return new ExprInterp().visit(ast, frame); + } + + private ClassSymbol findMainClass() { + for (ClassDecl classDecl : classDecls) { + if (classDecl.name.equals("Main")) + return classDecl.sym; + } + throw new StaticError("No Main class found"); + } + + public ClassDecl findClassDecl(TypeSymbol typeSym) { + for (ClassDecl cd : classDecls) { + if (cd.sym == typeSym) + return cd; + } + + throw new StaticError("No such type " + typeSym.name); + } + + class MethodInterp extends cd.ir.AstVisitor { + + public boolean earlyReturn = false; + + @SuppressWarnings("serial") + class EarlyReturnException extends RuntimeException { + public JlValue value; + public EarlyReturnException(final JlValue value) { + this.value = value; + } + } + + @Override + public JlValue assign(Assign ast, final StackFrame frame) { + + new AstVisitor() { + + @Override + public Void field(Field ast, Expr right) { + JlValue obj = expr(ast.arg(), frame); + assert obj != null && obj.asRef() != null; + final JlValue val = expr(right, frame); + assert val != null; + obj.asRef().setField(ast.sym, val); + return null; + } + + @Override + public Void index(Index ast, Expr right) { + JlValue obj = expr(ast.left(), frame); + JlValue idx = expr(ast.right(), frame); + final JlValue val = expr(right, frame); + assert val != null; + obj.asRef().setDeref(idx.asInt(), val); + return null; + } + + @Override + public Void var(Var ast, Expr right) { + final JlValue val = expr(right, frame); + assert val != null; + frame.setVar(ast.sym, val); + return null; + } + + @Override + protected Void dflt(Ast ast, Expr arg) { + throw new StaticError("Malformed l-value in AST"); + } + + }.visit(ast.left(), ast.right()); + + return null; + } + + @Override + public JlValue builtInWrite(BuiltInWrite ast, StackFrame frame) { + + JlValue val = expr(ast.arg(), frame); + + try { + output.write(Integer.toString(val.asInt())); + } catch (IOException e) { + throw new DynamicError(e, ExitCode.INTERNAL_ERROR); + } + + return null; + + } + + @Override + public JlValue builtInWriteln(BuiltInWriteln ast, StackFrame arg) { + + try { + output.write("\n"); + } catch (IOException e) { + throw new DynamicError(e, ExitCode.INTERNAL_ERROR); + } + + return null; + } + + @Override + public JlValue ifElse(IfElse ast, StackFrame frame) { + + JlValue cond = expr(ast.condition(), frame); + + if (cond.asBoolean()) { + return visit(ast.then(), frame); + } else { + return visit(ast.otherwise(), frame); + } + + } + + @Override + public JlValue methodCall(MethodCall ast, final StackFrame frame) { + return expr(ast.getMethodCallExpr(), frame); + } + + @Override + public JlValue whileLoop(WhileLoop ast, StackFrame frame) { + + while (true) { + + JlValue cond = expr(ast.condition(), frame); + + if (!cond.asBoolean()) { + return null; + } + + visit(ast.body(), frame); + } + + } + + @Override + public JlValue returnStmt(ReturnStmt ast, StackFrame frame) { + + JlValue ret = null; + + if (ast.arg() != null) { + ret = expr(ast.arg(), frame); + } else { + ret = new JlNull(); + } + + if (!earlyReturn) { + return ret; + } else { + throw new EarlyReturnException(ret); + } + + } + + } + + public MethodDecl findMethodDecl(ClassSymbol typeSym, + final String methodName) { + ClassSymbol currSym = typeSym; + while (currSym != ClassSymbol.objectType) { + ClassDecl cd = findClassDecl(currSym); + final List result = new ArrayList(); + + for (Ast mem : cd.members()) { + AstVisitor vis = new AstVisitor() { + @Override + public Void methodDecl(MethodDecl ast, Void arg) { + + if (!ast.name.equals(methodName)) { + return null; + } + result.add(ast); + + return null; + } + }; + + vis.visit(mem, null); + } + + if (result.size() == 1) { + return result.get(0); + } + + if (result.size() > 1) { + throw new StaticError(result.size() + + " implementations of method " + methodName + + " found in type " + currSym.name); + } + + currSym = currSym.superClass; + } + + throw new StaticError("No method " + methodName + " in type " + typeSym); + } + + // Note: does not interpret phis! + public JlValue interpretCfg(ControlFlowGraph cfg, final StackFrame frame) { + + BasicBlock current = cfg.start; + MethodInterp minterp = new MethodInterp(); + JlValue res = null; + + while (true) { + step(); + + for(Stmt stmt : current.stmts) { + res = minterp.visit(stmt, frame); + if(stmt instanceof ReturnStmt) + return res; + } + + if (current == cfg.end) { + return res; + } else if (current.condition == null) { + current = current.successors.get(0); + } else { + + JlValue cond = expr(current.condition, frame); + + if (cond.asBoolean()) { + current = current.trueSuccessor(); + } else { + current = current.falseSuccessor(); + } + + } + + } + + } + + + public JlValue invokeMethod(final ClassSymbol typeSym, + final String methodName, final JlObject rcvr, + final List arguments) { + MethodDecl mdecl = findMethodDecl(typeSym, methodName); + StackFrame newFrame = new StackFrame(rcvr); + int idx = 0; + + for (VariableSymbol sym : mdecl.sym.parameters) { + newFrame.setVar(sym, arguments.get(idx++)); + } + + if (mdecl.cfg != null) { + return interpretCfg(mdecl.cfg, newFrame); + } else { + final MethodInterp mthInterp = new MethodInterp(); + mthInterp.earlyReturn = true; + try { + return mthInterp.visit(mdecl.body(), newFrame); + } catch (final EarlyReturnException ex) { + return ex.value; + } + + } + + } +} diff --git a/test/cd/backend/interpreter/JlValue.java b/test/cd/backend/interpreter/JlValue.java new file mode 100644 index 0000000..3c2f585 --- /dev/null +++ b/test/cd/backend/interpreter/JlValue.java @@ -0,0 +1,544 @@ +package cd.backend.interpreter; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import cd.backend.ExitCode; +import cd.ir.Symbol.ArrayTypeSymbol; +import cd.ir.Symbol.ClassSymbol; +import cd.ir.Symbol.PrimitiveTypeSymbol; +import cd.ir.Symbol.TypeSymbol; +import cd.ir.Symbol.VariableSymbol; + +// Values: +abstract class JlValue { + public final TypeSymbol typeSym; + + static public JlValue getDefault(TypeSymbol type) { + JlValue result = null; + if (type.isReferenceType()) + result = new JlNull(); + else if (type == PrimitiveTypeSymbol.booleanType) + result = new JlBoolean(false); + else if (type == PrimitiveTypeSymbol.intType) + result = new JlInt(0); + assert result != null; + return result; + } + + public JlValue(TypeSymbol s) { + typeSym = s; + } + + protected void defaultConversion() { + throw new StaticError("Type conversion between incompatible types."); + } + + public boolean asBoolean() { + defaultConversion(); + return false; + } + + public int asInt() { + defaultConversion(); + return 0; + } + + public JlReference asRef() { + defaultConversion(); + return null; + } + + public JlObject asObject() { + defaultConversion(); + return null; + } + + // Binary Operations + // Arithmetic + protected JlValue defaultOperation() { + throw new StaticError("Operation not supported on " + this.getClass()); + } + + public JlValue times(JlValue value) { + return defaultOperation(); + } + + public JlValue div(JlValue value) { + return defaultOperation(); + } + + public JlValue add(JlValue value) { + return defaultOperation(); + } + + public JlValue subtract(JlValue value) { + return defaultOperation(); + } + + public JlValue mod(JlValue value) { + return defaultOperation(); + } + + // Boolean + public JlValue or(JlValue value) { + return defaultOperation(); + } + + public JlValue and(JlValue value) { + return defaultOperation(); + } + + // Comparison + public JlValue less(JlValue value) { + return defaultOperation(); + } + + public JlValue lessOrEqual(JlValue value) { + return defaultOperation(); + } + + public JlValue greater(JlValue value) { + return defaultOperation(); + } + + public JlValue greaterOrEqual(JlValue value) { + return defaultOperation(); + } + + // Unary Operation + public JlValue plus() { + return defaultOperation(); + } + + public JlValue minus() { + return defaultOperation(); + } + + public JlValue not() { + return defaultOperation(); + } +} + +abstract class JlReference extends JlValue { + + public JlReference(TypeSymbol s) { + super(s); + } + + @Override + public JlReference asRef() { + return this; + } + + public abstract JlValue field(VariableSymbol name); + + public abstract void setField(VariableSymbol name, JlValue val); + + public abstract JlValue deref(int index); + + public abstract void setDeref(int index, JlValue val); + + public abstract boolean canBeCastTo(String typeName); + + @Override + public String toString() { + return String.format("%s(%x)", typeSym, System.identityHashCode(this)); + } + +} + +class JlObject extends JlReference { + + protected Map fields = new HashMap(); + + public JlObject(ClassSymbol s) { + super(s); + } + + @Override + public JlObject asObject() { + return this; + } + + @Override + public JlValue field(VariableSymbol name) { + if (fields.containsKey(name)) { + return fields.get(name); + } + + JlValue dflt = JlValue.getDefault(name.type); + setField(name, dflt); + return dflt; + } + + @Override + public void setField(VariableSymbol name, JlValue val) { + fields.put(name, val); + } + + @Override + public JlValue deref(int index) { + throw new StaticError("Not an array"); + } + + @Override + public void setDeref(int index, JlValue val) { + throw new StaticError("Not an array"); + } + + @Override + public boolean canBeCastTo(String typeName) { + // Can always cast to Object: + if (typeName.equals("Object")) + return true; + + // Make up a set of acceptable types. Check for circular loops! + Set superTypes = new HashSet(); + ClassSymbol currentType = (ClassSymbol)typeSym; + + while (!currentType.name.equals("Object")) { + if (superTypes.contains(currentType.name)) { + throw new StaticError("Circular inheritance: " + currentType.name); + } + + superTypes.add(currentType.name); + currentType = currentType.superClass; + } + + return superTypes.contains(typeName); + } +} + +class JlArray extends JlReference { + + private JlValue contents[]; + + public JlArray(ArrayTypeSymbol s, int size) { + super(s); + + if (size < 0) { + throw new DynamicError("Invalid array size: " + size, + ExitCode.INVALID_ARRAY_SIZE); + } + + this.contents = new JlValue[size]; + for (int i = 0; i < size; i++) { + this.contents[i] = JlValue.getDefault(s.elementType); + } + + } + + @Override + public JlReference asRef() { + return this; + } + + public JlArray asArray() { + return this; + } + + @Override + public JlValue deref(int idx) { + + try { + return contents[idx]; + } catch (final ArrayIndexOutOfBoundsException ex) { + throw new DynamicError("Array index out of bounds " + idx, + ExitCode.INVALID_ARRAY_BOUNDS); + } + + } + + @Override + public void setDeref(int idx, JlValue value) { + + try { + contents[idx] = value; + } catch (final ArrayIndexOutOfBoundsException ex) { + throw new DynamicError("Array index out of bounds " + idx, + ExitCode.INVALID_ARRAY_BOUNDS); + } + + } + + @Override + public JlValue field(VariableSymbol name) { + throw new StaticError("Not an object"); + } + + @Override + public void setField(VariableSymbol name, JlValue value) { + throw new StaticError("Not an object"); + } + + @Override + public boolean canBeCastTo(String typeName) { + return this.typeSym.name.equals(typeName) || typeName.equals("Object"); + } + +} + +class JlNull extends JlReference { + + public JlNull() { + super(ClassSymbol.nullType); + } + + @Override + public boolean equals(Object o) { + + if (o instanceof JlNull) { + return true; + } + + return false; + } + + @Override + public JlReference asRef() { + return this; + } + + @Override + public JlObject asObject() { + throw new DynamicError("Null pointer dereferenced", + ExitCode.NULL_POINTER); + } + + @Override + public JlValue field(VariableSymbol name) { + throw new DynamicError("Null pointer dereferenced", + ExitCode.NULL_POINTER); + } + + @Override + public void setField(VariableSymbol name, JlValue value) { + throw new DynamicError("Null pointer dereferenced", + ExitCode.NULL_POINTER); + } + + @Override + public JlValue deref(int idx) { + throw new DynamicError("Null pointer dereferenced", + ExitCode.NULL_POINTER); + } + + @Override + public void setDeref(int idx, JlValue value) { + throw new DynamicError("Null pointer dereferenced", + ExitCode.NULL_POINTER); + } + + @Override + public boolean canBeCastTo(String typeName) { + return true; + } + + @Override + public String toString() { + return "null"; + } + +} + +class JlInt extends JlValue { + + private final int value; + + JlInt(int value) { + super(PrimitiveTypeSymbol.intType); + this.value = value; + } + + @Override + public boolean equals(Object o) { + + if (o instanceof JlInt) { + return value == ((JlInt) o).value; + } + + return false; + } + + @Override + public JlValue times(JlValue obj) { + + if (obj instanceof JlInt) { + return new JlInt(this.value * ((JlInt) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public JlValue div(JlValue obj) { + + if (obj instanceof JlInt) { + return new JlInt(this.value / ((JlInt) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public JlValue mod(JlValue obj) { + + if (obj instanceof JlInt) { + return new JlInt(this.value % ((JlInt) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public JlValue add(JlValue obj) { + + if (obj instanceof JlInt) { + return new JlInt(this.value + ((JlInt) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public JlValue subtract(JlValue obj) { + + if (obj instanceof JlInt) { + return new JlInt(this.value - ((JlInt) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public JlValue less(JlValue obj) { + + if (obj instanceof JlInt) { + return new JlBoolean(this.value < ((JlInt) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public JlValue lessOrEqual(JlValue obj) { + + if (obj instanceof JlInt) { + return new JlBoolean(this.value <= ((JlInt) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public JlValue greater(JlValue obj) { + + if (obj instanceof JlInt) { + return new JlBoolean(this.value > ((JlInt) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public JlValue greaterOrEqual(JlValue obj) { + + if (obj instanceof JlInt) { + return new JlBoolean(this.value >= ((JlInt) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public JlValue plus() { + return new JlInt(value); + } + + @Override + public JlValue minus() { + return new JlInt(-value); + } + + @Override + public int asInt() { + return value; + } + + @Override + public String toString() { + return Integer.toString(value); + } + +} + +class JlBoolean extends JlValue { + + private final boolean value; + + JlBoolean(boolean value) { + super(PrimitiveTypeSymbol.booleanType); + this.value = value; + } + + @Override + public boolean equals(Object o) { + + if (o instanceof JlBoolean) { + return value == ((JlBoolean) o).value; + } + + return false; + + } + + @Override + public JlValue not() { + return new JlBoolean(!value); + }; + + @Override + public JlValue or(JlValue obj) { + + if (obj instanceof JlBoolean) { + return new JlBoolean(this.value || ((JlBoolean) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public JlValue and(JlValue obj) { + + if (obj instanceof JlBoolean) { + return new JlBoolean(this.value && ((JlBoolean) obj).value); + } + + throw new DynamicError("Invalid type!", ExitCode.INTERNAL_ERROR); + + } + + @Override + public boolean asBoolean() { + return value; + } + + @Override + public String toString() { + return Boolean.toString(value); + } + +} \ No newline at end of file diff --git a/test/cd/backend/interpreter/StackFrame.java b/test/cd/backend/interpreter/StackFrame.java new file mode 100644 index 0000000..e6c8f59 --- /dev/null +++ b/test/cd/backend/interpreter/StackFrame.java @@ -0,0 +1,41 @@ +package cd.backend.interpreter; + +import java.util.HashMap; +import java.util.Map; + +import cd.ir.Symbol.VariableSymbol; + +// The stack: +class StackFrame { + + private final JlObject thisPointer; + private final Map variables; + + public StackFrame(JlObject thisPointer) { + this.thisPointer = thisPointer; + this.variables = new HashMap(); + } + + public JlValue var(VariableSymbol name) { + if (variables.containsKey(name)) { + return variables.get(name); + } + + JlValue dflt = JlValue.getDefault(name.type); + setVar(name, dflt); + return dflt; + } + + public void setVar(VariableSymbol name, JlValue val) { + variables.put(name, val); + } + + public String toString() { + return String.format("StackFrame(%s) {%s}", System.identityHashCode(this), variables.toString()); + } + + public JlObject getThisPointer() { + return thisPointer; + } + +} \ No newline at end of file diff --git a/test/cd/backend/interpreter/StaticError.java b/test/cd/backend/interpreter/StaticError.java new file mode 100644 index 0000000..9931b10 --- /dev/null +++ b/test/cd/backend/interpreter/StaticError.java @@ -0,0 +1,10 @@ +package cd.backend.interpreter; + +/** Thrown in cases that should be ruled out by static analysis: */ +@SuppressWarnings("serial") +public +class StaticError extends RuntimeException { + public StaticError(String message) { + super(message); + } +} \ No newline at end of file