Homework 4

This commit is contained in:
Carlos Galindo 2020-01-15 22:34:57 +01:00
parent 0afc86ceeb
commit 72cc3206c4
Signed by: kauron
GPG Key ID: 83E68706DEE119A3
125 changed files with 4200 additions and 1636 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
}
}

View File

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

View File

@ -0,0 +1,10 @@
/* Test access an array on a null pointer */
class Main {
void main() {
int[] x;
x = null;
x[1] = 5; //this should throw null pointer exception
}
}

View File

@ -0,0 +1,9 @@
/* Test access an array on a null pointer */
class Main {
void main() {
int[] x;
x[1] = 5; //this should throw null pointer exception
}
}

View File

@ -0,0 +1,10 @@
/* Test creating an array with negative length */
class Main {
void main() {
int[] x;
x = new int[-3];
x[0] = 3;
x[1] = 4;
x[2] = 5;
}
}

View File

@ -0,0 +1,16 @@
/* Test accessing arrays */
class Main {
void main() {
int[] x;
int i;
x = new int[3];
x[0] = 3;
x[1] = 4;
x[2] = 5;
i = x[0] + x[1] + x[2];
x[2]=55;
write(i);
writeln();
}
}

View File

@ -0,0 +1,15 @@
/* Test creating arrays of objects and accessing a null element*/
class A{}
class Main {
void main() {
A[] x;
A a;
x = new A[3];
x[1] = new A();
x[2] = a;
x[2] = new A();
}
}

View File

@ -0,0 +1,8 @@
/* Test array size=0 */
class Main {
void main() {
int[] x;
x = new int[0];
}
}

View File

@ -0,0 +1,12 @@
/* Test Arrays as Fields */
class Main {
int[] x;
void main() {
int i;
x = new int[3];
x[0] = 3;
x[1] = 4;
x[2] = 5;
}
}

View File

@ -0,0 +1,19 @@
/* Test inherited Array */
class Main {
void main() {
C1 c1;
C2 c2;
c1 = new C1();
c2 = new C2();
c1.x = new int[3];
c2.x = new int[4];
}
}
class C1{
int[] x;
}
class C2 extends C1 {}

View File

@ -0,0 +1,11 @@
/* Test creating arrays of objects */
class A{}
class Main {
void main() {
A[] x;
x = new A[2];
x[0] = new A();
}
}

View File

@ -0,0 +1,14 @@
// any array is a subtype of Object
// assignment and 'is equal' with an object of type 'Object' is fine
class Main {
void main() {
boolean y;
Object x;
int[] testArray;
testArray = new int[10];
x = testArray;
testArray = null;
x = null;
}
}

View File

@ -0,0 +1,21 @@
// assignment of subtypes
class Main {
void main() {
A a1, a2;
B b1, b2;
C c;
a1 = new A();
b2 = new B();
c = new C();
a2 = a1;
b1 = c;
}
}
class A {}
class B extends A {}
class C extends B {}

View File

@ -0,0 +1,8 @@
// Test boolean && and || operators
class Main {
void main() {
boolean a, b, c;
a = b && c || a;
}
}

View File

@ -0,0 +1,31 @@
// Test the equal/not equal operations, one of the types must be a subtype of the other
class Main {
void main() {
C1 c1;
C2 c2;
C3 c3;
Object o1, o2;
boolean s;
o1 = new Object();
o2 = new Object();
c1 = new C1();
c2 = new C2();
c3 = new C3();
s = o1 == o2;
s = c1 != c2;
s = c3 == c1;
s = o2 != o1;
s = null == c2;
s = o1 == c2;
//s = null == o;
}
}
class C1 {}
class C2 extends C1{}
class C3 extends C2{}

View File

@ -0,0 +1,15 @@
// Test the equal/not equal operations, one of the types must be a subtype of the other
class Main {
void main() {
int a,b;
boolean c;
a = 8;
b = 6;
c = a==b;
if (!c){
write(5);}
}
}

View File

@ -0,0 +1,14 @@
// Test the equal/not equal operations, one of the types must be a subtype of the other
class Main {
void main() {
boolean a,b,c;
a = true;
b = 6<10;
c = a==b;
if (c){
write(5);}
}
}

View File

@ -0,0 +1,36 @@
// Test '!' operator
class Main {
void main() {
boolean a,b,c,d;
int x,y;
a = true;
c = !false;
b = !a;
x = 100;
y = 5;
if (a) {
write(1);}
writeln();
if (!b){
write(2);
}
writeln();
while(c){
write(3);
c = false;
}
writeln();
// !x < 2 * y --> !x type error
// !(x < 2 * y) --> correct syntax
while(!(x<2*y)){
write(y);
y = y*2;
}
}
}

View File

@ -0,0 +1,21 @@
// Test types in a Cast (cannot cast to unrelated type)
class Main {
void main() {
C1 a, b;
C2 c, d;
Object o;
c = new C2();
a = new C1();
b = (C1) c;
d = (C2) a;
o = (Object) a;
o = (Object) c;
o = (Main) c;
}
}
class C1 {}
class C2 extends C1{}

View File

@ -0,0 +1,14 @@
// Test downcast from Object to array
class Main {
void main() {
A[] a;
Object o;
o = new Object();
a = (A[]) o;
}
}
class A{}

View File

@ -0,0 +1,12 @@
// Test types in a Cast
// cannot cast an int to an boolean
class Main {
void main() {
int a;
boolean b;
a = 1;
b = (boolean) a;
}
}

View File

@ -0,0 +1,11 @@
// Test types in a Cast (cannot cast to unrelated type)
class Main {
void main() {
int a;
boolean b;
//b = false;
a = (int) b;
}
}

View File

@ -0,0 +1,12 @@
// Test downcast from array to Object
class Main {
void main() {
int[] x;
Object o;
x = new int[5];
o = (Object) x;
}
}

View File

@ -0,0 +1,14 @@
// Test downcast from array to Object
class Main {
void main() {
A[] a;
Object o;
a = new A[5];
o = (Object) a;
}
}
class A{}

View File

@ -0,0 +1,15 @@
// Test cast from a type to the same type
class Main {
void main() {
C c,d;
Object o,g;
c = new C();
g = new Object();
d = (C) c;
o = (Object) g;
}
}
class C {}

View File

@ -0,0 +1,26 @@
// Test valid casts
class Main {
void main() {
C1 a, x;
C2 c,d;
C4 b,f;
Object o;
c = new C2();
a = new C1();
b = new C4();
f = new C4();
x = (C1) b;
o = (Object) f;
}
}
class C1 {}
class C2 extends C1{}
class C3 extends C2{}
class C4 extends C3{}

View File

@ -0,0 +1,23 @@
// Test casts from type to object
class Main {
void main() {
C1 a;
Object o,f,g;
int x;
boolean y;
a = new C1();
x = 2;
y = true;
o = (Object) a;
//f = (Object) x;
//g = (Object) y;
}
}
class C1 {}

View File

@ -13,7 +13,7 @@ class Main {
i6 = 6;
i7 = 7;
write(i0 + (i1 + ( i2 + ( i3 + ( i4 + (i5 + (i6 + i7))))))); writeln();
write(i0 + (i1 + ( i2 + ( i3 + ( i4 + (i5 + (i6 + (i7 + i0)))))))); writeln();
write(((((((i0 + i1) + i2) + i3) + i4) + i5) + i6) + i7); writeln();
write(((i0 + i1) + (i2 + i3)) + ((i4 + i5) + (i6 + i7))); writeln();
}

View File

@ -0,0 +1,10 @@
// test division by zero
class Main {
void main() {
int a;
int b;
a = 10;
b = a / 0;
}
}

View File

@ -0,0 +1,27 @@
// access a field from a class and a superclass (also includes hidden fields)
class Main {
void main() {
C1 c1;
C2 c2;
C4 c4;
c1 = new C1();
c2 = new C2();
c4 = new C4();
c1.a = 5;
c2.a = 6;
c4.a = false;
}
}
class C1{
int a;
}
class C2 extends C1 {}
class C3 extends C2 {}
class C4 extends C3 {
boolean a;
}

View File

@ -0,0 +1,19 @@
// test an Array of Objects and access their field
class Main {
void main() {
D[] x;
x = new D[50];
x[22].c.a = 2;
}
}
class C{
int a;
}
class D {
C c;
}

View File

@ -0,0 +1,5 @@
class Main {
void main() {
write(0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15);
}
}

View File

@ -0,0 +1,30 @@
// Access inherited and hidden fields (general check)
class Other {
int a;
int b;
Object c;
Other o;
}
class Main extends Other {
int a, b;
void main() {
Other o;
o = (Other) this;
a = 1;
b = 2;
o.a = -1;
o.b = -2;
write(a);
write(b);
write(o.a);
write(o.b);
if (c != null) {
if (o.o != null) {
write(0);
}
}
}
}

View File

@ -0,0 +1,34 @@
/* testing assign statements*/
class Main {
int methodCall() { return 0; }
int methodCall2(int a, int b) {
if (a >= b) {
return 0;
} else {
if (b <= a) {
return 1;
}
}
return a * b;
}
void main() {
int a, b, c, e;
Ast d;
int[] g;
Object f;
a = read();
b = methodCall();
c = methodCall2(a, b);
d = new Ast();
e = d.field;
g = new int[a];
f = new Object[a];
f = g;
g = (int[]) f;
}
}
class Ast {
int field;
}

View File

@ -0,0 +1 @@
1

View File

@ -8,11 +8,11 @@ class Main {
d = 40;
e = 1;
while ( b > c ) {
while ( b >= c ) {
write(b);
b = b-1;
}
a = c < (b+4-5/2);
a = c <= (b+4-5/2);
if (a){
write(13);}
if (100/2>d*e){

View File

@ -0,0 +1,17 @@
// call an method from a superclass
class Main {
void main() {
C2 c;
c = new C2();
c.aux();
}
}
class C1{
void aux(){
write(2);
}
}
class C2 extends C1 {}

View File

@ -0,0 +1,10 @@
class Main {
void main() {
while(false) {}
if(false) {
return;
}
if (false) {} else {}
if (false) {} else {return;}
}
}

View File

@ -0,0 +1,5 @@
class Main {
void main() {
while(false) {}
}
}

View File

@ -0,0 +1,16 @@
// Hide a field from a superclass (type doesn't need to match)
class Parent {
int a;
}
class Main extends Parent {
Object a;
Main b;
void main() {
Parent c;
b = new Main();
b.a = new Object();
c = new Parent();
c.a = 10;
}
}

View File

@ -7,14 +7,10 @@ class Main {
int a;
boolean b;
Object c;
a = null;
b = null;
c = null;
write(a);
writeln();
//write(null)
}
}

View File

@ -0,0 +1,14 @@
/* test overwritten main method */
class OtherMain {
void main () {
int a;
}
}
class Main extends OtherMain {
void main () {
int b;
}
}

View File

@ -0,0 +1,32 @@
/* this test forces the compiler to push registers to the stack */
class Main {
void main() {
int u,v,x,y,z;
boolean e,f,g,h,i,j;
B b1,b2;
e = true;
f = true;
g = true;
z = 0;
while (z < 30) {
if (f){
if (g){
u = 5;
v = 8;
x = u/v;
b1 = new B();
e = false;
}
}
else {
j = true;
}
z = z + 1;
}
}
}
class B {}

View File

@ -0,0 +1,29 @@
/* Test expression evaluation with more than 8 variables
Test Register use
*/
class Main {
void main() {
int r1, r2, r3;
int i0, i1, i2, i3, i4, i5, i6, i7;
int a,b,c,d,e;
i0 = 0;
i1 = 1;
i2 = 2;
i3 = 3;
i4 = 4;
i5 = 5;
i6 = 6;
i7 = 7;
a = 5;
b = 5;
c = 5;
d = 5;
e = 5;
write(i0 + (i1 + ( i2 + ( i3 + (a+b+c+d+e) + ( i4 + (i5 + (i6 + (i7 + i0)))))))); writeln();
write(((((((i0 + i1) + i2) + i3) + i4 + (a*b*c*d*e)) + i5) + i6) + i7); writeln();
write(((i0 + i1) + (i2 + i3)) + ((i4 + i5) + (i6 + i7))); writeln();
}
}

View File

@ -0,0 +1,9 @@
/* Test that variables are zero initialized */
class Main {
void main() {
int a;
write(a);
}
}

View File

@ -0,0 +1,79 @@
// A if/else block is valid return statment if all possible branches return some value
// Also serves to check subtyping
// The return logic leads to nonsensical programs with statements after the return, but that's ok
class Main {
// All returns must be empty, but we don't care for their existence
void main() {
int a, b, c;
if (a == b) {
return;
} else {
while (a > b) {
return;
}
if (c > b) {
return;
}
}
}
// All returns must be null, Main (all subtypes of Main), and there must be an unconditional return
Main action1() {
Main a;
Object b;
boolean x, y;
if (x) {
if (y) {
if (x || y) {
if (x == y && x != y) {
return null;
} else {
return a;
}
} else {
if (b == null) {
return a;
} else {
return a;
}
}
} else {
if (b == a || a == null) {
return null;
} else {
return a;
}
}
} else {
return null;
}
}
// All returns must be null, Main[] (all subtypes of Main[]), and there must be an unconditional return
Main[] action2() {
Main[] a, b;
if (a == b) {
return a;
} else {
if (b != null) {
return b;
} else {
return null;
}
}
}
// All returns must be int (no subtypes), and there must be an unconditional return
int action3() {
int a, b, c, d;
if (a > b) {
return c - d;
while (a != a) {
return b;
}
} else {
return c * d / a + b % c - a;
}
}
}

View File

@ -0,0 +1,9 @@
class Main {
int n() {
return 0;
}
void main() {
n();
}
}

View File

@ -0,0 +1,21 @@
// The return statement can be placed in the middle of the method, and no warning will be thrown about unexecuted
// statements
class Main {
void main() {
write(things());
writeln();
}
int things() {
int a;
a = 10;
return a;
// These statements won't be reached
// but there is a complete return statement
a = 20;
writeln();
writeln();
}
}

View File

@ -0,0 +1,22 @@
/* test method that returns an object */
class Main {
void main() {
int x;
B b;
b = aux();
x = b.a;
write(x);
}
B aux(){
B b;
b = new B();
b.a = 10;
return b;
}
}
class B {
int a;
}

View File

@ -0,0 +1,24 @@
// A if/else with a return in both branches is valid
class Main {
void main() {
write(things());
writeln();
}
int things() {
int a;
int b;
Object c, d;
a = 10;
b = 100;
c = new Object();
d = new Object();
if (c == d) {
return 10;
} else {
return 5;
}
}
}

View File

@ -0,0 +1,22 @@
// test that call-by-value is used
class Main {
void main() {
int x;
A a;
a = new A();
a.a = 50;
aux(a.a);
x = a.a;
write(x);
}
void aux (int arg){
arg = arg + 1;
}
}
class A {
int a;
}

View File

@ -0,0 +1,18 @@
// test that call-by-value is used
class Main {
void main() {
int[] y;
int x;
y = new int[5];
y[2] = 50;
aux(y[2]);
x = y[2];
write(x);
}
void aux (int arg){
arg = arg + 1;
}
}

View File

@ -0,0 +1,18 @@
// test that call-by-value is used
class Main {
void main() {
int[] y;
int x;
y = new int[5];
y[2] = 50;
aux(y);
x = y[2];
write(x);
}
void aux (int[] arg){
arg[2] = 100;
}
}

View File

@ -0,0 +1,17 @@
// call an method from a superclass
class Main {
void main() {
C2 c;
c = new C2();
c.aux();
}
}
class C1{
void aux(){
write(2);
}
}
class C2 extends C1 {}

View File

@ -0,0 +1,19 @@
// call a simple method and use its return value
class Main {
void main() {
int a,b;
C c;
c = new C();
a = 5;
b = c.aux(a);
}
}
class C{
int aux(int arg){
return arg*2;
}
}

View File

@ -0,0 +1,35 @@
// call a method with many parameters and use its return value
// also Test Register use, by allocating memory
class Main {
void main() {
int a,b,c,d,e,f,g,h,x;
a = 5;
b = 5;
c = 5;
d = 5;
e = 5;
f = 5;
g = 5;
h = 5;
x = aux(a,b,c,d,e,f,g,h);
}
int aux(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8){
int[] x;
int i;
D d;
x = new int[20];
i = arg1 + arg2 + arg3 + arg4 - arg5 - arg6 + arg7 - arg8;
d = new D();
return i;
}
}
class D{
int[] x;
}

View File

@ -1,24 +0,0 @@
/* testing arrays with primitive types as well as new objects
the if/else statements shows that the array is boolean array is initialized with false
*/
class Main {
void main() {
int [] testArray;
boolean [] boolarray;
boolarray = new boolean [3];
testArray = new int [10];
testArray[5] = 3;
boolarray[1] = true;
if (boolarray[0]){
write(1);}
else{
write(5);}
writeln();
if (boolarray[1]){
write(1);}
}
}

View File

@ -1,12 +0,0 @@
/* testing assign statements*/
class Main {
void main() {
a = read();
b = methodCall();
c = methodCall(param1, param2);
d = object.access;
e = new Ast();
d = new int[size];
f = new Object[size];
}
}

View File

@ -1,21 +0,0 @@
class Main {
void main() {
int a, b, c, d, e;
a = 10;
b = -1;
c = 0;
d = 100;
e = 2;
write(a / b); writeln();
write(d / e); writeln();
write(c / d); writeln();
write(b / a + c); writeln();
write(d / e * a / b * c); writeln();
write(d / e * a / b); writeln();
write(d / e + a / b); writeln();
write(d / e * a * b * c); writeln();
write(d / e * a - b + c); writeln();
}
}

View File

@ -1,9 +0,0 @@
/* testing basic inheritance*/
class C1 {}
class C2 extends C1 {}
class C3 extends C2 {}
class C4 extends C2 {}
class C5 extends C3 {}
class Main {
void main() {}
}

View File

@ -1,22 +0,0 @@
/* testing different expressions
compiler should recognize Type error: Return statement of method with void return type should not have arguments
*/
class Main {
void main() {
return;
return true;
return false;
return 0x10;
return 10;
return variable;
return array[index];
return methodAccess();
return object.field;
return object.call();
return op + op2;
return op / asd * asd && a == true;
return this.run();
return this;
return this.field;
}
}

View File

@ -1,31 +0,0 @@
class Main {
void main() {
int r1;
int i0, i1;
int x,y,z;
i0 = 5;
i1 = 2;
r1 = i1 * 3;
write(r1); writeln();
r1 = i0 * i1;
write(r1); writeln();
r1 = r1 * i0 * i1 * 3;
write(r1); writeln();
y = 5;
z = 10;
x = (-y * z);
write(x); writeln();
y = 0;
z = - 10;
x = (y * z);
write(x); writeln();
write(y * z); writeln();
write(0 * -10); writeln();
}
}

View File

@ -1,8 +0,0 @@
/*Check the order of the declarations in the generated parser
* Do the variables come always first or in their place? */
class ClassName {
void a() {}
int a;
void a() {}
void tests(boolean d, nulle a) {}
}

View File

@ -1,25 +0,0 @@
/* Test what happens for Integers that are to big for 32bits
2147483647 (=0x7FFFFFFF) is the biggest integer in 32bits (IntMAX)
*/
class Main {
void main() {
int x,y,z;
x = 2147483647;
write( x ); writeln();
/* add 1 to IntMax and output it */
write( x + 1); writeln();
/* read an int bigger than IntMAX */
x = read();
write(x); writeln();
/* performe some operation that should generate an int overflow */
x = 21474836400;
y = 60000;
write( x + y); writeln();
z = 20000000;
write( y * z); writeln();
write( (y * z) ); writeln();
}
}

View File

@ -1 +0,0 @@
2147483647

View File

@ -1,15 +0,0 @@
/*Testing all related to methods (declaration and execution)*/
class Main {
void main() {
callWithParams(a, b, c, 0, false);
object.call(a, b, d);
}
int method(int a, String b, int[] c) {
}
int[] method2() {}
Object method3() {}
Model[] method4() {}
}

View File

@ -1,26 +0,0 @@
/* Test read/write native functions */
class Main {
void main() {
int r1, r2, readvar, a1;
r1 = 6;
/* r2 = 22 */
r2 = read();
write(r1); writeln(); // 6
/* test expressions inside write() */
write(r1 - 3); writeln(); // 3
write(r1 - 6); writeln(); // 0
write(r1 - 7); writeln(); // -1
/* should output 111 */
readvar = read(); // 1
write( (r1 + (r2 * 5)) + readvar); // 117
write(- r1); // -6
writeln();
/* should output 15 */
a1 = read(); // -15
write(- a1); // 15
}
}

View File

@ -1,6 +0,0 @@
22
1
-15

View File

@ -1,34 +0,0 @@
/* testing different statements
'condition' is not initialized
*/
class Main {
void main() {
if (condition) {
instructions();
asd.b = c;
if (cond2) {
} else {
nonEmptyBlock = a;
}
} else {
}
// Whiles
while (condition) {
while (anotherLoop == false) {
nestedLoops();
}
}
while (false) {} // emptyloop
// Returns
return; // empty
return expr; // with expressions (expressions already tested)
return array[index];
// Writes
write(a);
write(9 + 10);
writeln();
write(call());
}
}

View File

@ -1,12 +0,0 @@
/* testing invalid casts */
class Main
{
void main()
{
int a;
Object d;
d = new Object();
a = (Object) d;
a = (boolean[]) a;
}
}

View File

@ -1,14 +0,0 @@
class Main {
void main() {
boolean b;
int a,c,n;
a = 10;
//b = true;
while ( a>0 ) {
a = a-1;
}
}

View File

@ -1,24 +0,0 @@
/* test what happens if there are no parentheses */
class Main {
void main() {
int x;
int y;
int z;
x = 5;
y = 10;
z = 100;
write( x + y + z); writeln();
/* */
write( - x + y - z); writeln();
/* should output 205 */
write( x + 2 * z); writeln();
write( x + 2 * z / x + 1); writeln();
write(+x); writeln();
}
}

View File

@ -10,11 +10,6 @@ public class Config {
MACOSX
}
/**
* What kind of system we are on
*/
public static final SystemKind systemKind;
/**
* Defines the extension used for assembler files on this platform.
* Currently always {@code .s}.
@ -84,11 +79,9 @@ public class Config {
public static final String JAVA_EXE;
static {
final String os = System.getProperty("os.name").toLowerCase();
if(os.contains("windows") || os.contains("nt")) {
systemKind = SystemKind.WINDOWS;
BINARYEXT = ".exe";
MAIN = "_main";
PRINTF = "_printf";
@ -108,7 +101,6 @@ public class Config {
COMMENT_SEP = "#";
}
else if(os.contains("mac os x") || os.contains("darwin")) {
systemKind = SystemKind.MACOSX;
BINARYEXT = ".bin";
MAIN = "_main";
PRINTF = "_printf";
@ -126,7 +118,6 @@ public class Config {
COMMENT_SEP = "#";
}
else {
systemKind = SystemKind.LINUX;
BINARYEXT = ".bin";
MAIN = "main";
PRINTF = "printf";

View File

@ -1,19 +1,6 @@
package cd;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import cd.backend.codegen.AstCodeGenerator;
import cd.frontend.parser.JavaliAstVisitor;
import cd.frontend.parser.JavaliLexer;
import cd.frontend.parser.JavaliParser;
@ -24,6 +11,14 @@ import cd.ir.Ast.ClassDecl;
import cd.ir.Symbol;
import cd.ir.Symbol.TypeSymbol;
import cd.util.debug.AstDump;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* The main entrypoint for the compiler. Consists of a series
@ -37,7 +32,7 @@ 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 (Advanced Compiler Design)
// Set to non-null to write dump of control flow graph
public File cfgdumpbase;
/** Symbol for the Main type */
@ -74,6 +69,12 @@ public class Main {
// Run the semantic check:
m.semanticCheck(astRoots);
// Generate code:
String sFile = arg + Config.ASMEXT;
try (FileWriter fout = new FileWriter(sFile)) {
m.generateCode(astRoots, fout);
}
}
}
}
@ -111,6 +112,13 @@ public class Main {
new SemanticAnalyzer(this).check(astRoots);
}
}
public void generateCode(List<ClassDecl> astRoots, Writer out) {
{
AstCodeGenerator cg = AstCodeGenerator.createCodeGenerator(this, out);
cg.go(astRoots);
}
}
/** Dumps the AST to the debug stream */
private void dumpAst(List<ClassDecl> astRoots) throws IOException {

View File

@ -0,0 +1,19 @@
package cd.backend;
public enum ExitCode {
OK(0),
INVALID_DOWNCAST(1),
INVALID_ARRAY_STORE(2),
INVALID_ARRAY_BOUNDS(3),
NULL_POINTER(4),
INVALID_ARRAY_SIZE(5),
INFINITE_LOOP(6),
DIVISION_BY_ZERO(7),
INTERNAL_ERROR(22);
public final int value;
private ExitCode(int value) {
this.value = value;
}
}

View File

@ -0,0 +1,176 @@
package cd.backend.codegen;
import cd.Config;
import cd.backend.codegen.RegisterManager.Register;
import java.io.IOException;
import java.io.Writer;
public class AssemblyEmitter {
public Writer out;
public StringBuilder indent = new StringBuilder();
public int counter = 0;
public AssemblyEmitter(Writer out) {
this.out = out;
}
/** Creates an constant operand. */
static String constant(int i) {
return "$" + i;
}
/** Creates an constant operand with the address of a label. */
static String labelAddress(String lbl) {
return "$" + lbl;
}
/** 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 */
static String arrayAddress(Register arrReg, Register idxReg) {
final int offset = Config.SIZEOF_PTR * 2; // one word each in front for
// 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);
}
void increaseIndent(String comment) {
indent.append(" ");
if (comment != null)
emitComment(comment);
}
void decreaseIndent() {
indent.setLength(indent.length() - 2);
}
void emitCommentSection(String name) {
int indentLen = indent.length();
int breakLen = 68 - indentLen - name.length();
StringBuffer sb = new StringBuffer();
sb.append(Config.COMMENT_SEP).append(" ");
for (int i = 0; i < indentLen; i++)
sb.append("_");
sb.append(name);
for (int i = 0; i < breakLen; i++)
sb.append("_");
try {
out.write(sb.toString());
out.write("\n");
} catch (IOException e) {
}
}
void emitComment(String comment) {
emitRaw(Config.COMMENT_SEP + " " + comment);
}
void emit(String op, Register src, String dest) {
emit(op, src.repr, dest);
}
void emit(String op, String src, Register dest) {
emit(op, src, dest.repr);
}
void emit(String op, Register src, Register dest) {
emit(op, src.repr, dest.repr);
}
void emit(String op, String src, String dest) {
emitRaw(String.format("%s %s, %s", op, src, dest));
}
void emit(String op, int src, Register dest) {
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);
}
void emit(String op, Register reg) {
emit(op, reg.repr);
}
void emit(String op, int dest) {
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);
}
void emitMove(Register src, Register dest) {
emitMove(src.repr, dest.repr);
}
void emitMove(String src, Register dest) {
emitMove(src, dest.repr);
}
void emitMove(String src, String dest) {
if (!src.equals(dest))
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);
}
void emitStore(Register src, int destOffset, Register dest) {
emitStore(src.repr, destOffset, dest);
}
void emitStore(String src, int destOffset, Register dest) {
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));
}
String uniqueLabel() {
String labelName = "label" + counter++;
return labelName;
}
void emitLabel(String label) {
try {
out.write(label + ":" + "\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
void emitRaw(String op) {
try {
out.write(indent.toString());
out.write(op);
out.write("\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,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;
}
}

View File

@ -0,0 +1,172 @@
package cd.backend.codegen;
import cd.Config;
import cd.Main;
import cd.backend.codegen.RegisterManager.Register;
import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Symbol.*;
import java.io.Writer;
import java.util.*;
import static cd.Config.MAIN;
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 final Main main;
final AssemblyEmitter emit;
final RegisterManager rm = new RegisterManager();
AstCodeGenerator(Main main, Writer out) {
initMethodData();
main.allTypeSymbols = new ArrayList<>();
this.emit = new AssemblyEmitter(out);
this.main = main;
this.rnv = new RegsNeededVisitor();
this.eg = new ExprGenerator(this);
this.sg = new StmtGenerator(this);
}
protected void debug(String format, Object... args) {
this.main.debug(format, args);
}
public static AstCodeGenerator createCodeGenerator(Main main, Writer out) {
return new AstCodeGenerator(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.
*
* <p>
* The generated file will be divided into two sections:
* <ol>
* <li>Prologue: Generated by {@link #emitPrefix()}. This contains any
* introductory declarations and the like.
* <li>Body: Generated by {@link ExprGenerator}. This contains the main
* method definitions.
* </ol>
*/
public void go(List<? extends ClassDecl> 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)
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<TypeSymbol> elementTypes = new ArrayList<>();
elementTypes.add(ClassSymbol.objectType);
elementTypes.add(PrimitiveTypeSymbol.intType);
elementTypes.add(PrimitiveTypeSymbol.booleanType);
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 the new Main().main() code to start the program:
// 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);
// 2. Create main variable
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);
}
void initMethodData() {
rm.initRegisters();
}
void emitMethodSuffix(boolean returnNull) {
if (returnNull)
emit.emit("movl", 0, Register.EAX);
emit.emitRaw("leave");
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));
}
}
}

View File

@ -0,0 +1,613 @@
package cd.backend.codegen;
import cd.backend.ExitCode;
import cd.backend.codegen.AstCodeGenerator.*;
import cd.backend.codegen.RegisterManager.Register;
import cd.ir.Ast.*;
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.util.debug.AstOneLine;
import java.util.*;
import static cd.Config.SCANF;
import static cd.backend.codegen.AstCodeGenerator.*;
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<Register,Location> {
private final AstCodeGenerator cg;
ExprGenerator(AstCodeGenerator astCodeGenerator) {
cg = astCodeGenerator;
}
public Register gen(Expr ast) {
return visit(ast, null);
}
@Override
public Register visit(Expr ast, Location arg) {
try {
cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast));
if (cg.rnv.calc(ast) > cg.rm.availableRegisters()) {
Deque<Register> 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);
} finally {
cg.emit.decreaseIndent();
}
}
@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);
}
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<Register> 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);
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");
}
cg.emit.emitMove(FALSE, leftReg);
cg.emit.emitLabel(endLabel);
}
cg.rm.releaseRegister(rightReg);
return leftReg;
}
@Override
public Register booleanConst(BooleanConst ast, Location arg) {
Register reg = cg.rm.getRegister();
cg.emit.emitMove(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<arraySize
Register arraySizeReg = cg.rm.getRegister();
cg.emit.emitLabel(checkBoundsLabel);
cg.emit.emitLoad(1 * VAR_SIZE, pointer, arraySizeReg);
cg.emit.emit("cmp", 0, index);
cg.emit.emit("jl", invalidBoundLabel); // 0 > 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);
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:
* <ul>
* <li>Type of array (for casts, assignments and equalities)</li>
* <li>Size of array (N)</li>
* <li>element 0 or pointer to element 0</li>
* <li>...</li>
* <li>element N or pointer to element N</li>
* </ul>
* The type of the elements is stored in the vtable for the array (in order to
* save space). <br/>
*
* 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)
* <ul>
* <li>Pointer to type vtable</li>
* <li>hashCode0, field0</li>
* <li>...</li>
* <li>hashCodeN, fieldN</li>
* </ul>
* 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;
}
@Override
public Register nullConst(NullConst ast, Location arg) {
Register reg = cg.rm.getRegister();
cg.emit.emitMove(0, 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;
}
/**
* 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<Register> callerSaved = new ArrayList<>();
for (Register reg : CALLER_SAVE) {
if (cg.rm.isInUse(reg)) {
callerSaved.add(0, reg);
cg.emit.emit("push", Register.EAX);
}
}
// 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);
}
// 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);
// 3. Pop arguments from stack
cg.emit.emit("add", VAR_SIZE * ast.allArguments().size(), STACK_REG);
// 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;
}
@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;
}
/**
* 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;
switch (ast.sym.kind) {
case PARAM:
offset = 3 + arg.methodSym().parameters.indexOf(ast.sym);
break;
case LOCAL:
offset = -(1 + positionOfLocal(arg.methodSym(), ast.name));
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");
}
if (obtainReference)
cg.emit.emit("add", offset * VAR_SIZE, register);
else
cg.emit.emitLoad(offset * VAR_SIZE, register, register);
return register;
}
private int positionOfLocal(MethodSymbol sym, String name) {
List<String> 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;
}
}

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