Homework 4

This commit is contained in:
Carlos Galindo 2020-01-15 22:34:57 +01:00
commit 72cc3206c4
Signed by: kauron
GPG key ID: 83E68706DEE119A3
125 changed files with 4208 additions and 1644 deletions

View file

@ -2,10 +2,10 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="lib" path="lib/antlr-4.4-complete.jar"/>
<classpathentry kind="lib" path="lib/junit-4.12.jar"/>
<classpathentry kind="lib" path="lib/hamcrest-core-1.3.jar"/>
<classpathentry kind="lib" path="lib/antlr-4.7.1-complete.jar"/>
<classpathentry kind="lib" path="lib/javaliParserObf.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Javali-HW3</name>
<name>Javali-HW4</name>
<comment></comment>
<projects>
</projects>

View file

@ -1,2 +0,0 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View file

@ -1,6 +1,39 @@
Grade: 25/25
nop90: 29/30 points
105/105 tests passed [20/20]
Comments:
I found that the following tasks were not implemented correctly or contained errors.
Submitted test cases: [5/5]
* 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

144
README.md
View file

@ -1,55 +1,111 @@
# Compiler design
## Parser and syntactic analysis
See repo containing [Homework #2](https://svn.inf.ethz.ch/svn/trg/cd_students/ss18/teams/nop90/HW2).
# HW4: code generation
Better viewed as markdown
## Semantic analysis
This project checks a list class declaration as trees of Ast nodes for
possible semantic errors.
# VTables
They are generated when each class is visited, except for those of Object,
Object[], int[] and boolean[], which are generated in the bootstrapping.
### Design
The semantic checks described [here](./SemanticChecks.md) are implemented in three
phases.
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.
1. Circular inheritance check (just with the names, and checking for non-existent
types) to avoid stack overflow problems in the main analysis. May throw a
`NO_SUCH_TYPE` error if the parent of a given class doesn't exist.
2. Main semantic analysis, performed by `StmtAnalyzer` and `ExprAnalyzer`,
two visitors that traverse each `ClassDecl` and all of its contents looking
for semantic errors. Some types and methods may not have been discovered
when they are accessed, and to that end the functions `findType(String)`
and `findMethod(String,ClassSymbol)` lookup both, searching unvisited class
and method declarations in order to find the corresponding symbol.
3. Lookup of start point (method `void main()` in `Main` class)
supertype pointer (Object) <-- vtable pointer
element vtable pointer (unused)
### Testing
A set of test cases (Javali programs, valid and invalid) are provided. The ones
designed specifically for this homework assignment are located in
`javali_tests/HW3_nop90`, and they cover all the code in the `cd.frontend.semantic`
and `cd.ir` packages. They cover all the kinds of errors that can arise
from a semantic analysis of Javali programs. As the order of the errors
is not determined, each test case only checks one failing condition, and
therefore many more test cases are needed for this homework than for
previous ones.
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).
The test files are organized in folders, with names ending in Tests or Errors,
the latter only contain test cases that result in semantic errors.
supertype pointer <-- vtable pointer
hash("method0")
method0 pointer
hash("method1")
method1 pointer
...
hash("methodN")
methodN pointer
To run the tests that exist in the `javali_tests` folder you can simply run
# Method calls
$ ant clean clean-test compile test
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:
### Development environment
This project is designed for Eclipse and GNU/Linux, but it can be run
in other environments, as long as they have a 32bit `IA 86x assembly` compiler
and a `JVM`. Check the `Config` class for more information.
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.
### Documentation
Available as [javadoc](javadoc/index.html). It is generated running the following command:
Therefore, the stack frame follows the following structure (N arguments
and M local variables):
$ ant clean-doc generate-doc
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
### Important files
* `TODO.md`: contains tasks to complete
* `SemanticChecks.md`: contains the semantic checks numbered for quick reference
* [Javali Grammar](https://www.ethz.ch/content/dam/ethz/special-interest/infk/inst-cs/lst-dam/documents/Education/Classes/Spring2018/210_Compiler_Design/Homework/javali.pdf)
* [HW3 statement](https://www.ethz.ch/content/dam/ethz/special-interest/infk/inst-cs/lst-dam/documents/Education/Classes/Spring2018/210_Compiler_Design/Homework/hw3.pdf)
# 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

View file

@ -1,43 +0,0 @@
# Class and method declaration errors
1. All programs must have a method main() with void return type in the class Main (`INVALID_START_POINT`)
2. The superclass of each class exists (`NO_SUCH_TYPE`)
3. There is no circular inheritance (`CIRCULAR_INHERITANCE`)
4. No class is declared with the name `Object` (`OBJECT_CLASS_DEFINED`)
5. Unique names (`DOUBLE_DECLARATION`) for each group:
1. Class names
2. Field names in a single class (field shadowing is allowed)
3. Method names in a single class (Override is allowed, overloading not)
4. Method parameters and local variable names
6. Overridden methods must: (`INVALID_OVERRIDE`)
1. Have the same number of parameters
2. The return types must match
3. The type of the parameters must match (name can differ)
# Method body errors
1. `write()` must take one argument of type `int` (`TYPE_ERROR`)
2. `read()` and `writeln()` must take no arguments (`WRONG_NUMBER_OF_ARGUMENTS`)
3. The condition of a `while` or `if` statement must be typed `booelan` (`TYPE_ERROR`)
4. Assignments: the type of the right-hand side must be a suptype of the type of the left-hand side. (`TYPE_ERROR`)
5. Operators (`TYPE_ERROR`):
1. `*`, `/`, `%`, `+`, `-`, `<`, `>`, `<=`, `>=` take `int` arguments only.
2. `!`, `&&`, `||` take `boolean` arguments only.
3. `==` and `!=` takes any couple of arguments as long as one of the types is a subtype of the other.
4. A cast is only possible if one of the types is a subtype of the other
6. Method invocation, must match with declaration:
1. The number of parameters (`WRONG_NUMBER_OF_ARGUMENTS`)
2. The type of each of the parameters (`TYPE_ERROR`)
7. Arrays:
1. When indexing, the index expression must be an `int` (`TYPE_ERROR`)
2. When creating one, the index expression must be an `int` (`TYPE_ERROR`)
8. The constant `null` can be assigned to any reference type.
9. The type name in a `new` statement must exist (`NO_SUCH_TYPE`)
10. The left-hand side of an assignment can only be: (`NOT_ASSIGNABLE`)
1. A variable
2. A field
3. Array dereferences (indexing)
11. There must be no attempt to access the field or method of a
non-object type, such as arrays or primitive types (`TYPE_ERROR`)
12. A method with a return type must have a return statement at the end
of all its paths (`MISSING_RETURN`). A while loop is not a valid return
regardless of the condition. A ifElse must have a return on both the
else and otherwise block. There may be some statements after a return.

75
TODO.md
View file

@ -1,75 +0,0 @@
## Possible code changes
### General
* DONE Use more specific messages when throwing a `SemanticFailure` exception. This has the caveat that the automated testing relies on those tests?
### StmtAnalyzer
* DONE Propagate the change of class parameter from `ExprAnalyzer`.
* DONE Complete checks for `MISSING_RETURN`, some ideas:
* DONE Assume no instructions will appear after a return statement and analyze only the last `Stmt` using other visitor
with different class parameters (for example passing the desired type and returning a `Optional<TypeSymbol>`).
* DONE Decide the logic behind a return statement in a `while` loop, maybe treat it as an `if` with an empty `else` block.
* A `while` as last instruction is not a valid return.
* A `if` as last instruction must have a valid return a the end of its then and otherwise blocks.
### ExprAnalyzer
* DONE Change the second class parameter to give method AND class information (needed by var and this nodes)
* DISCARDED Add a ClassSymbol to MethodSymbol and use it as class parameter, the problem arises when visiting a method,
should an empty method with the proper class be provided? Could also be solved by using `Symbol` as class parameter.
* DONE Create a new class that includes a `ClassSymbol` and a `MethodSymbol` and use that. Keep `MethodSymbol == null`
when appropriate.
## Tests
This tests have been selected to increase to 100% the code coverage obtained by running the tests designed for the previous HW,
which are in folder svn/HW2/javali_tests/HW2_nop90
As they are mostly related to errors that haven't been triggered, each test should be separate, in order to generate all
of the errors.
### Remaining
### Done
* access var from class or superclass
* test ! operator
* type error in unaryOp
* type error in array size
* call method from superclass
* call a method that doesn't exist
* call a method with the wrong type of params
* call a method with the wrong number of params
* assign to all possible syntactically correct options
* field
* var
* array index
* method (error)
* this (error)
* indexing both types error
* try to access a field of something that is not a class type
* typing error in cast
* AND and OR operations
* equals operators (a case that is OK and another that's not)
* binary operation with wrong type (any but == and !=)
* search for a type that doesn't exist in a method body
* don't have a Main class
* don't have a main() method in the Main class (all possibilities)
* wrong name
* wrong return value
* wrong parameters
* inherited main method (it should work)
* write something that is not an integer
* declare a variable mutiple times (local variables)
* declare a local variable with the same name as a parameter
* check MISSING_RETURN
* nested ifElse as end of method
* while as end of method
* normal returns at the end of the method
* return with further statements after it
* while with non-int condition
* use a array type that hasn't been discovered yet
* ```class A { B[] name; } class B {}```
* name a class Object
* name a class like another class
* name a parameter like another
* override methods and try to break that
* same return type
* same number of params
* same type of params
* name a variable like another (field level)

107
build.xml
View file

@ -15,31 +15,26 @@
<property name="antlr.jar" value="${basedir}/lib/antlr-4.4-complete.jar"/>
<property name="antlr.profile" value="false"/>
<property name="antlr.report" value="false"/>
<property name="jacocoant.jar" value="${basedir}/lib/jacocoant.jar"/>
<property name="coverage.file" location="${build.dir}/jacoco.exec"/>
<property name="min.coverage" value="0.5"/>
<property name="coverage.check" value="cd.frontend.*:cd.backend.*"/>
<property name="doc.dir" value="javadoc"/>
<!-- Cleans generated code, but NOT the parser source! -->
<target name="clean">
<delete dir="${build.dir}"/>
</target>
<target name="compile">
<mkdir dir="${build.dir}"/>
<target name="compile">
<mkdir dir="${build.dir}"/>
<javac debug="true" destdir="${build.dir}" includeantruntime="false">
<src path="${src.dir}"/>
<src path="${test.dir}"/>
<classpath>
<pathelement location="${antlr.jar}"/>
<pathelement location="${junit.jar}"/>
<pathelement location="${hamcrest.jar}"/>
<pathelement location="${parser.jar}"/>
</classpath>
</javac>
</target>
<javac debug="true" destdir="${build.dir}" includeantruntime="false">
<src path="${src.dir}"/>
<src path="${test.dir}"/>
<classpath>
<pathelement location="${antlr.jar}"/>
<pathelement location="${junit.jar}"/>
<pathelement location="${hamcrest.jar}"/>
<pathelement location="${parser.jar}"/>
</classpath>
</javac>
</target>
<!-- Deletes all byproducts of running the tests -->
<target name="clean-test">
@ -49,70 +44,28 @@
<fileset dir="${javali_tests.dir}" includes="**/*.bin"/>
<fileset dir="${javali_tests.dir}" includes="**/*.dot"/>
<fileset dir="${javali_tests.dir}" includes="**/*.exe"/>
<fileset dir="${javali_tests.dir}" includes="**/*.ref"/>
<fileset dir="${javali_tests.dir}" includes="**/*.ref"/>
</delete>
</target>
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="${jacocoant.jar}"/>
</taskdef>
<!-- Runs the tests. Use the compile target first! -->
<target name="test" depends="compile">
<jacoco:coverage destfile="${coverage.file}">
<junit fork="true" forkmode="once" failureproperty="tests-failed" outputtoformatters="false">
<formatter type="brief" usefile="false"/>
<batchtest skipNonTests="true">
<fileset dir="bin" includes="**/*.class" />
</batchtest>
<assertions enablesystemassertions="true" />
<sysproperty key="cd.meta_hidden.Version" value="REFERENCE" />
<classpath>
<pathelement location="${build.dir}"/>
<pathelement location="${junit.jar}"/>
<pathelement location="${hamcrest.jar}"/>
<pathelement location="${antlr.jar}"/>
<pathelement location="${parser.jar}"/>
</classpath>
</junit>
</jacoco:coverage>
<fail if="tests-failed" />
<junit fork="true" forkmode="once" failureproperty="tests-failed" outputtoformatters="false">
<formatter type="brief" usefile="false"/>
<batchtest skipNonTests="true">
<fileset dir="bin" includes="**/*.class" />
</batchtest>
<assertions enablesystemassertions="true" />
<sysproperty key="cd.meta_hidden.Version" value="REFERENCE" />
<classpath>
<pathelement location="${build.dir}"/>
<pathelement location="${junit.jar}"/>
<pathelement location="${hamcrest.jar}"/>
<pathelement location="${antlr.jar}"/>
<pathelement location="${parser.jar}"/>
</classpath>
</junit>
<fail if="tests-failed" />
</target>
<target name="test-coverage-report">
<jacoco:report>
<executiondata>
<file file="${coverage.file}"/>
</executiondata>
<structure name="JaCoCo Ant Example">
<classfiles>
<fileset dir="${build.dir}"/>
</classfiles>
<sourcefiles encoding="UTF-8">
<fileset dir="${src.dir}"/>
</sourcefiles>
</structure>
<check>
<rule element="PACKAGE" includes="${coverage.check}">
<limit counter="LINE" value="COVEREDRATIO" minimum="${min.coverage}"/>
</rule>
</check>
</jacoco:report>
<echo message="Coverage above ${min.coverage} in ${coverage.check}: OK" level="info"/>
</target>
<target name="clean-doc">
<delete dir="javadoc"/>
</target>
<target name="generate-doc">
<javadoc sourcepath="${src.dir}" destdir="${doc.dir}"
private="true" use="true" author="true">
<package name="cd"/>
<package name="cd.frontend.semantic"/>
<package name="cd.ir"/>
<arg value="-notimestamp"/>
</javadoc>
</target>
</project>

View file

@ -0,0 +1,15 @@
/* Test illegal downcasts */
class A { }
class B extends A { }
class Main {
void main() {
A a;
B b;
a = new A();
b = (B) a; /* Should fail at runtime */
write(0);
writeln();
}
}

View file

@ -0,0 +1,23 @@
class Main {
void main() {
int a;
a = fib(20);
write(a);
writeln();
}
int fib(int n) {
int fib;
int fib2;
if (n <= 1) {
fib = n;
} else {
fib = fib(n-1);
fib2 = fib(n-2);
fib = fib + fib2;
}
return fib;
}
}

View file

@ -0,0 +1,14 @@
/* Test expressions using array elements as operands */
class Main {
int[] x;
void main() {
int i;
x = new int[3];
x[0] = 3;
x[1] = 4;
x[2] = 5;
i = x[0] + x[1] + x[2];
write(i);
writeln();
}
}

View file

@ -0,0 +1,20 @@
/* Test access to parameters and fields */
class A {
int i;
void foo(int p) {
write(p);
write(i);
writeln();
}
}
class Main {
void main() {
A a;
a = new A();
a.i = 10;
a.foo(1);
a.foo(2);
a.foo(3);
}
}

View file

@ -0,0 +1,28 @@
/* Test read() with different kinds of LHS values */
class Main {
int x;
void main() {
int y;
int[] arr;
write(1);
writeln();
y = read();
write(y + 1);
writeln();
x = read();
write(x + 1);
writeln();
arr = new int[64];
arr[x] = read();
write(arr[x] + 1);
writeln();
}
}

View file

@ -0,0 +1,4 @@
9
51
12

View file

@ -0,0 +1,14 @@
/* Test legal downcasts */
class A { }
class B extends A { }
class Main {
void main() {
A a;
B b;
a = new B();
b = (B) a; /* OK at runtime */
write(0);
}
}

View file

@ -0,0 +1,29 @@
/* Test access to fields from elements of arrays */
class A {
int field;
void foo() {
write(1);
write(field);
writeln();
}
}
class Main {
A[] x;
void main() {
int i;
x = new A[2];
i = 1;
write(i);
writeln();
x[i] = new A();
x[i].field = i + 1;
i = x[1].field;
write(i);
writeln();
x[1].foo();
}
}

View file

@ -0,0 +1,50 @@
/* Test virtual method calls */
class A {
void override() {
write(0);
writeln();
}
void base() {
write(1);
writeln();
}
}
class B extends A {
void override() {
write(2);
writeln();
}
void sub() {
write(3);
writeln();
}
}
class Main {
void main() {
A a;
B b;
a = null;
b = null;
a = new A();
a.base();
a.override();
b = new B();
b.base();
b.override();
b.sub();
a = b;
a.base();
a.override();
b.base();
b.override();
b.sub();
}
}

View file

@ -0,0 +1,81 @@
/* Overall test of arrays, loops, etc. that does a simple quicksort */
class Record {
int a;
void print() {
write(a);
writeln();
}
}
class Main {
Record [] a;
int i;
void swap(Record r1, Record r2) {
int temp;
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) / 2;
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 = 5;
a = new Record[SIZE];
j = 0;
while (j < SIZE) {
a[j] = new Record();
j = j + 1;
}
a[0].a = 5;
a[1].a = 3;
a[2].a = 1;
a[3].a = 4;
a[4].a = 2;
/* Numbers before sorting */
j = 0;
while (j < SIZE) {
a[j].print();
j = j + 1;
}
writeln();
sort(0, 4);
/* Numbers after sorting */
j = 0;
while (j < SIZE) {
a[j].print();
j = j + 1;
}
writeln();
}
}

View file

@ -0,0 +1,12 @@
/* Test accessing arrays with invalid index*/
class Main {
void main() {
int[] x;
x = new int[5];
x[0] = 3;
x[1] = 4;
x[2] = 5;
x[5] = 5; //this should fail
}
}

View file

@ -0,0 +1,9 @@
/* Test accessing arrays with invalid index*/
class Main {
void main() {
int[] x;
x = new int[5];
x[8] = 5; //this should fail
}
}

Some files were not shown because too many files have changed in this diff Show more