Homework B (benchmarks)

This commit is contained in:
Carlos Galindo 2020-01-15 22:38:07 +01:00
parent 72cc3206c4
commit 76fbabdf53
Signed by: kauron
GPG Key ID: 83E68706DEE119A3
141 changed files with 7540 additions and 2032 deletions

View File

@ -5,7 +5,7 @@
<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="lib" path="lib/javaliParserObf.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

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

View File

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

View File

@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

View File

@ -1,39 +1,15 @@
nop90: 29/30 points
Comments:
I found that the following tasks were not implemented correctly or contained errors.
* Null pointers check doesn't work for methods:
"
class Main {
void main() {
A a;
a.m();
}
}
class A {
void m() { }
}
"
* Side effects
If methods have side effects, then the execution order matters:
"
class Main {
void main() {
write(m1() + m2());
writeln();
}
int m1() {
write(1);
return 1;
}
int m2() {
write(2);
return 2;
}
}
"
* Can't compile programs which use many registers
team name: nop90
task 1 correctness: 95
task 1 score: 541.3
task 1 points (max 25 points): 16
task 2 deadline 1 correctness: 100
task 2 deadline 1 score: 14.1
task 2 deadline 1 points: 1
task 2 deadline 2 correctness: 97
task 2 deadline 2 score: 264.5
task 2 deadline 2 points: 15
task 2 runtimeOpts correctness: 98
task 2 runtimeOpts score: 265.3
task 2 runtimeOpts bonus points: 0
task 2 points (max 25 points): 15
HWB TOTAL (max 50 points): 31

16
Javali tests.launch Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/Javali"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=Javali"/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="Javali"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dcd.meta_hidden.Version=REFERENCE"/>
</launchConfiguration>

111
README.md
View File

@ -1,111 +0,0 @@
# HW4: code generation
Better viewed as markdown
# VTables
They are generated when each class is visited, except for those of Object,
Object[], int[] and boolean[], which are generated in the bootstrapping.
When the object vtable is generated, so is the array table, which just
contains a reference to the supertype (in our case `Object`) and an unused
pointer to the declared type of the element's vtable.
supertype pointer (Object) <-- vtable pointer
element vtable pointer (unused)
Each type's vtable contains the supertype as the first element (trait shared
with array type's vtables for easier cast type comparisons). Then they
contain all the methods available to an object of that type, including
all those inherited. As the dynamic type is not known, the number and
position of the methods is not known when executing a method, therefore
each method's pointer is prepended by the method's name hash (similar to
fields in an object).
supertype pointer <-- vtable pointer
hash("method0")
method0 pointer
hash("method1")
method1 pointer
...
hash("methodN")
methodN pointer
# Method calls
The basic scheme used for method calls and the structure of the stack
frame is that described in the `__cdecl` used in GCC. In short, this is
the execution of a method call:
1. The caller pushes the arguments in the stack, from right-to-left,
leaving the 0th argument (the reference to `this`) at the top of the stack.
2. The caller executes `call` (the instruction pointer gets stored in the stack)
3. The callee now takes control, and its first action is to save the base
pointer to the stack and set the base pointer to the current stack pointer.
Therefore, the arguments and return address are available to the method.
4. The local variables are reserved and zeroed in the stack.
5. The CALLEE_SAVED registers are saved to the stack.
6. The method executes its code
7. The CALLEE_SAVED registers are restored to their original status.
8. The callee restores the stack and base registers to its initial statuses.
This is accomplished with a `leave` instruction. Then it returns using `ret`.
This removes the instruction pointer from the stack leaving only the arguments.
9. The caller finally removes all the arguments from the stack and obtains the
return value from EAX.
Therefore, the stack frame follows the following structure (N arguments
and M local variables):
top of stack <-- ESP
...
saved registers
localM <-- EBP - 4 - M * 4
...
local1 <-- EBP - 8
local0 <-- EBP - 4
Old EBP <-- EBP
Return EIP
this <-- EBP + 8
arg0 <-- EBP + 12
...
argN <-- EBP + 12 + N * 4
# Variable and object representation
## Primitive variables
They are represented by value, with both integers and booleans occupying
4 bytes, and booleans being represented as TRUE = 0, FALSE = 1.
## Reference variables
Stored in the heap, using calloc (to zero-initialize them).
### Arrays
On top of storing the array elements, some overhead is stored with it,
namely a pointer to the array vtable for casts, and the size of the
array to check the bounds of the array on accesses or assignments.
The pointer to the array points to the vtable pointer (the first
element in the object structure):
array vtable pointer <-- var pointer
array size
element0
element1
...
elementN
### Objects
Stores a pointer to the object's type vtable and all the fields. As the
dynamic type of the object cannot be determined and therefore the fields
not statically ordered, each field occupies 8 bytes, with the first 4 containing
the hash for the VariableSymbol that represents it and the others the
value or reference to the field.
The hash allows hiding variables, but introduces an overhead when accessing
a field, to look for the position in which it is located.
vtable pointer <-- var pointer
field0's hash
field0
field1's hash
field1
...
fieldN's hash
fieldN

View File

@ -0,0 +1,60 @@
// Overall test of arrays, loops, etc. that does a simple quicksort.
class Record {
int a ;
}
class Main {
Record [] a;
int i;
void swap(Record r1, Record r2) {
int temp;
temp = 0+1;
temp = r1.a;
r1.a = r2.a;
r2.a = temp;
}
void sort(int left, int right) {
int i,j;
int m;
m = (a[left].a + a[right].a) / (1 + 1);
i = left;
j = right;
while (i <= j) {
while (a[i].a < m) { i = i+1; }
while (a[j].a > m) { j = j-1; }
if (i <= j) {
swap(a[i], a[j]);
i = i+1;
j = j-1;
}
}
if (left < j) { sort(left,j); }
if (i < right) { sort(i,right); }
}
void main() {
int SIZE;
int j;
SIZE = 10;
a = new Record[SIZE * 1];
j = 0;
while (j < SIZE) {
a[j] = new Record();
a[j].a = read();
j = j + 1;
}
sort(0, SIZE-1);
j = 0;
while (j < SIZE) {
i = a[j].a;
write(i);
writeln();
j = j + 1;
}
}
}

View File

@ -0,0 +1,10 @@
1
5
6
7
8
5
2
4
6
3

View File

@ -0,0 +1,40 @@
class Main {
int SIZE;
void main() {
int[] a;
int i;
int pi;
int e;
int thou;
pi = 3141;
e = 2718;
a = readStuff();
i = 0;
while(i < SIZE) {
write(pi * e * a[i] / (1000 * 1000));
writeln();
write(a[i] * pi * e / (1000 * 1000));
writeln();
i = i + 1;
}
}
int[] readStuff() {
int[] a;
int i;
SIZE = 5;
a = new int[SIZE];
i = 0;
while (i < SIZE) {
a[i] = read();
i = i + 1;
}
return a;
}
}

View File

@ -0,0 +1,5 @@
42
1337
9000
12345678
777

View File

@ -0,0 +1,13 @@
class Main {
void main() {
int i;
int j;
i = read();
j = 2 * i;
i = (i + 31) / 2;
j = (i * j) / 3;
write(i);
}
}

View File

@ -0,0 +1 @@
43

View File

@ -8,33 +8,40 @@
<property name="src.dir" value="${basedir}/src"/>
<property name="test.dir" value="${basedir}/test"/>
<property name="javali_tests.dir" value="${basedir}/javali_tests"/>
<property name="benchmarks.dir" value="${basedir}/benchmarks"/>
<property name="parser.dir" value="${src.dir}/cd/frontend/parser"/>
<property name="parser.jar" value="${basedir}/lib/javaliParserObf.jar"/>
<property name="build.dir" value="${basedir}/bin"/>
<property name="junit.jar" value="${basedir}/lib/junit-4.12.jar"/>
<property name="hamcrest.jar" value="${basedir}/lib/hamcrest-core-1.3.jar"/>
<property name="antlr.jar" value="${basedir}/lib/antlr-4.4-complete.jar"/>
<property name="antlr.jar" value="${basedir}/lib/antlr-4.7.1-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.*"/>
<!-- Cleans generated code, but NOT the parser source! -->
<target name="clean">
<delete dir="${build.dir}"/>
</target>
<target name="compile">
<mkdir dir="${build.dir}"/>
<!-- Builds the compiler framework for HW > HW1. -->
<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">
@ -44,28 +51,53 @@
<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"/>
<fileset dir="${benchmarks.dir}" includes="**/*.err"/>
<fileset dir="${benchmarks.dir}" includes="**/*.s"/>
<fileset dir="${benchmarks.dir}" includes="**/*.bin"/>
<fileset dir="${benchmarks.dir}" includes="**/*.dot"/>
<fileset dir="${benchmarks.dir}" includes="**/*.exe"/>
<fileset dir="${benchmarks.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">
<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" />
<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="BENCH" />
<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" />
</target>
<target name="bench" depends="compile">
<java classname="cd.BenchmarksRunner" fork="yes" error="bench-log.txt">
<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" />
<pathelement location="${antlr.jar}"/>
<pathelement location="${junit.jar}"/>
<pathelement location="${hamcrest.jar}"/>
<pathelement location="${parser.jar}"/>
</classpath>
</java>
</target>
</project>

0
javali_tests/HW1_nop90/Division.javali Executable file → Normal file
View File

0
javali_tests/HW1_nop90/EightVariablesWrite.javali Executable file → Normal file
View File

0
javali_tests/HW1_nop90/Multiplication.javali Executable file → Normal file
View File

0
javali_tests/HW1_nop90/Overflow.javali Executable file → Normal file
View File

0
javali_tests/HW1_nop90/Overflow.javali.in Executable file → Normal file
View File

0
javali_tests/HW1_nop90/ReadWrite.javali Executable file → Normal file
View File

0
javali_tests/HW1_nop90/ReadWrite.javali.in Executable file → Normal file
View File

0
javali_tests/HW1_nop90/UnaryOperators.javali Executable file → Normal file
View File

0
javali_tests/HW1_nop90/noParentheses.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/Casts.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/Division.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/EightVariablesWrite.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/Multiplication.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/NULLTest.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/Overflow.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/Overflow.javali.in Executable file → Normal file
View File

0
javali_tests/HW2_nop90/ReadWrite.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/ReadWrite.javali.in Executable file → Normal file
View File

0
javali_tests/HW2_nop90/UnaryOperators.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/conditionExpressions.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/invalidCasts.javali Executable file → Normal file
View File

0
javali_tests/HW2_nop90/noParentheses.javali Executable file → Normal file
View File

View File

@ -24,5 +24,3 @@ class Main {
}
}

0
javali_tests/HW4_nop90/Booleans/OkEquals2.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/Booleans/OkEquals3.javali Executable file → Normal file
View File

View File

View File

0
javali_tests/HW4_nop90/Casts/ErrPrimitiveCast.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/Casts/ErrPrimitiveCast2.javali Executable file → Normal file
View File

View File

View File

0
javali_tests/HW4_nop90/Casts/OkSubtype.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/Casts/OkTypeCast.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/Casts/OkTypeToObjectCast.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/EightVariablesWrite.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/ErrDivisionByZero.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/Fields/OkObjectFields.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/OkBoolBinaryOp.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/OkCasts.javali Executable file → Normal file
View File

View File

@ -0,0 +1,10 @@
/* Test that variables are zero initialized */
class Main {
void main() {
boolean a;
if (!a){
write(5); }
}
}

0
javali_tests/HW4_nop90/OkNullAssignment.javali Executable file → Normal file
View File

View File

@ -8,8 +8,7 @@ class Main {
e = true;
f = true;
g = true;
z = 0;
while (z < 30) {
while (e) {
if (f){
if (g){
u = 5;
@ -22,8 +21,6 @@ class Main {
else {
j = true;
}
z = z + 1;
}
}

0
javali_tests/HW4_nop90/OkUnaryOperators.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/OkVariables.javali Executable file → Normal file
View File

0
javali_tests/HW4_nop90/OkZeroInitialized.javali Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -0,0 +1,60 @@
// Overall test of arrays, loops, etc. that does a simple quicksort.
class Record {
int a ;
}
class Main {
Record [] a;
int i;
void swap(Record r1, Record r2) {
int temp;
temp = 0+1;
temp = r1.a;
r1.a = r2.a;
r2.a = temp;
}
void sort(int left, int right) {
int i,j;
int m;
m = (a[left].a + a[right].a) / (1 + 1);
i = left;
j = right;
while (i <= j) {
while (a[i].a < m) { i = i+1; }
while (a[j].a > m) { j = j-1; }
if (i <= j) {
swap(a[i], a[j]);
i = i+1;
j = j-1;
}
}
if (left < j) { sort(left,j); }
if (i < right) { sort(i,right); }
}
void main() {
int SIZE;
int j;
SIZE = 10;
a = new Record[SIZE * 1];
j = 0;
while (j < SIZE) {
a[j] = new Record();
a[j].a = read();
j = j + 1;
}
sort(0, SIZE-1);
j = 0;
while (j < SIZE) {
i = a[j].a;
write(i);
writeln();
j = j + 1;
}
}
}

View File

@ -0,0 +1,10 @@
1
5
6
7
8
5
2
4
6
3

View File

@ -4,136 +4,159 @@ import java.io.File;
public class Config {
public static enum SystemKind {
LINUX,
WINDOWS,
MACOSX
}
public static enum SystemKind {
LINUX,
WINDOWS,
MACOSX
}
/**
* Defines the extension used for assembler files on this platform.
* Currently always {@code .s}.
*/
public static final String ASMEXT = ".s";
public static final int TRUE = 1;
public static final int FALSE = 0;
/** Defines the extension used for binary files on this platform. */
public static final String BINARYEXT;
/**
* Defines the extension used for assembler files on this platform.
* Currently always {@code .s}.
*/
public static final String ASMEXT = ".s";
/** Defines the name of the main function to be used in .s file */
public static final String MAIN;
/**
* Defines the extension used for binary files on this platform.
*/
public static final String BINARYEXT;
/** Defines the name of the printf function to be used in .s file */
public static final String PRINTF;
/**
* Defines the name of the main function to be used in .s file
*/
public static final String MAIN;
/** Defines the name of the scanf function to be used in .s file */
public static final String SCANF;
/**
* Defines the name of the printf function to be used in .s file
*/
public static final String PRINTF;
/** Defines the name of the calloc function to be used in .s file */
public static final String CALLOC;
/**
* Defines the name of the scanf function to be used in .s file
*/
public static final String SCANF;
/** Defines the name of the exit function to be used in .s file */
public static final String EXIT;
/**
* Defines the name of the calloc function to be used in .s file
*/
public static final String CALLOC;
/** The assembler directive used to define a constant string */
public static final String DOT_STRING;
/**
* Defines the name of the exit function to be used in .s file
*/
public static final String EXIT;
/** The assembler directive used to define a constant int */
public static final String DOT_INT;
/**
* The assembler directive used to define a constant string
*/
public static final String DOT_STRING;
/** The assembler directive used to start the text section */
public static final String TEXT_SECTION;
/**
* The assembler directive used to define a constant int
*/
public static final String DOT_INT;
/** The assembler directive used to start the section for integer data */
public static final String DATA_INT_SECTION;
/**
* The assembler directive used to start the text section
*/
public static final String TEXT_SECTION;
/**
* The assembler directive used to start the section for string data (if
* different from {@link #DATA_INT_SECTION}
*/
public static final String DATA_STR_SECTION;
/**
* The assembler directive used to start the section for integer data
*/
public static final String DATA_INT_SECTION;
/** Comment separator used in assembly files */
public static final String COMMENT_SEP;
/**
* The assembler directive used to start the section for string data (if
* different from {@link #DATA_INT_SECTION}
*/
public static final String DATA_STR_SECTION;
/**
* Defines the assembler command to use. Should be a string array where each
* entry is one argument. Use the special string "$0" to refer to the output
* file, and $1 to refer to the ".s" file.
*/
public static final String[] ASM;
/**
* Comment separator used in assembly files
*/
public static final String COMMENT_SEP;
/**
* The directory from which to run the assembler. In a CYGWIN installation,
* this can make a big difference!
*/
public static final File ASM_DIR;
/**
* Defines the assembler command to use. Should be a string array where each
* entry is one argument. Use the special string "$0" to refer to the output
* file, and $1 to refer to the ".s" file.
*/
public static final String[] ASM;
/**
* sizeof a pointer in bytes in the target platform.
*/
public static final int SIZEOF_PTR = 4;
/**
* The directory from which to run the assembler. In a CYGWIN installation,
* this can make a big difference!
*/
public static final File ASM_DIR;
/**
* Name of java executable in JRE path
*/
/**
* sizeof a pointer in bytes in the target platform.
*/
public static final int SIZEOF_PTR = 4;
/**
* Name of java executable in JRE path
*/
public static final String JAVA_EXE;
static {
final String os = System.getProperty("os.name").toLowerCase();
static {
final String os = System.getProperty("os.name").toLowerCase();
if(os.contains("windows") || os.contains("nt")) {
BINARYEXT = ".exe";
MAIN = "_main";
PRINTF = "_printf";
SCANF = "_scanf";
CALLOC = "_calloc";
EXIT = "_exit";
// These are set up for a Cygwin installation on C:,
// you can change as needed.
ASM = new String[]{"gcc", "-o", "$0", "$1"};
ASM_DIR = new File("C:\\CYGWIN\\BIN");
JAVA_EXE = "javaw.exe";
DOT_STRING = ".string";
DOT_INT = ".int";
TEXT_SECTION = ".section .text";
DATA_INT_SECTION = ".section .data";
DATA_STR_SECTION = ".section .data";
COMMENT_SEP = "#";
}
else if(os.contains("mac os x") || os.contains("darwin")) {
BINARYEXT = ".bin";
MAIN = "_main";
PRINTF = "_printf";
SCANF = "_scanf";
CALLOC = "_calloc";
EXIT = "_exit";
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
ASM_DIR = new File(".");
JAVA_EXE = "java";
DOT_STRING = ".asciz";
DOT_INT = ".long";
TEXT_SECTION = ".text";
DATA_INT_SECTION = ".data";
DATA_STR_SECTION = ".cstring";
COMMENT_SEP = "#";
}
else {
BINARYEXT = ".bin";
MAIN = "main";
PRINTF = "printf";
SCANF = "scanf";
CALLOC = "calloc";
EXIT = "exit";
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
ASM_DIR = new File(".");
JAVA_EXE = "java";
DOT_STRING = ".string";
DOT_INT = ".int";
TEXT_SECTION = ".section .text";
DATA_INT_SECTION = ".section .data";
DATA_STR_SECTION = ".section .data";
COMMENT_SEP = "#";
}
}
if (os.contains("windows") || os.contains("nt")) {
BINARYEXT = ".exe";
MAIN = "_main";
PRINTF = "_printf";
SCANF = "_scanf";
CALLOC = "_calloc";
EXIT = "_exit";
// These are set up for a Cygwin installation on C:,
// you can change as needed.
ASM = new String[]{"gcc", "-o", "$0", "$1"};
ASM_DIR = new File("C:\\CYGWIN\\BIN");
JAVA_EXE = "javaw.exe";
DOT_STRING = ".string";
DOT_INT = ".int";
TEXT_SECTION = ".section .text";
DATA_INT_SECTION = ".section .data";
DATA_STR_SECTION = ".section .data";
COMMENT_SEP = "#";
} else if (os.contains("mac os x") || os.contains("darwin")) {
BINARYEXT = ".bin";
MAIN = "_main";
PRINTF = "_printf";
SCANF = "_scanf";
CALLOC = "_calloc";
EXIT = "_exit";
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
ASM_DIR = new File(".");
JAVA_EXE = "java";
DOT_STRING = ".asciz";
DOT_INT = ".long";
TEXT_SECTION = ".text";
DATA_INT_SECTION = ".data";
DATA_STR_SECTION = ".cstring";
COMMENT_SEP = "#";
} else {
BINARYEXT = ".bin";
MAIN = "main";
PRINTF = "printf";
SCANF = "scanf";
CALLOC = "calloc";
EXIT = "exit";
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
ASM_DIR = new File(".");
JAVA_EXE = "java";
DOT_STRING = ".string";
DOT_INT = ".int";
TEXT_SECTION = ".section .text";
DATA_INT_SECTION = ".section .data";
DATA_STR_SECTION = ".section .data";
COMMENT_SEP = "#";
}
}
}

View File

@ -1,6 +1,6 @@
package cd;
import cd.backend.codegen.AstCodeGenerator;
import cd.backend.codegen.CfgCodeGenerator;
import cd.frontend.parser.JavaliAstVisitor;
import cd.frontend.parser.JavaliLexer;
import cd.frontend.parser.JavaliParser;
@ -8,9 +8,13 @@ import cd.frontend.parser.JavaliParser.UnitContext;
import cd.frontend.parser.ParseFailure;
import cd.frontend.semantic.SemanticAnalyzer;
import cd.ir.Ast.ClassDecl;
import cd.ir.Symbol;
import cd.ir.Ast.MethodDecl;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.Symbol.TypeSymbol;
import cd.transform.AstOptimizer;
import cd.transform.CfgBuilder;
import cd.util.debug.AstDump;
import cd.util.debug.CfgDump;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.CommonTokenStream;
@ -21,12 +25,11 @@ import java.util.ArrayList;
import java.util.List;
/**
* The main entrypoint for the compiler. Consists of a series
* of routines which must be invoked in order. The main()
* routine here invokes these routines, as does the unit testing
* code. This is not the <b>best</b> programming practice, as the
* series of calls to be invoked is duplicated in two places in the
* code, but it will do for now. */
* The main entrypoint for the compiler. Consists of a series of routines which must be invoked in
* order. The main() routine here invokes these routines, as does the unit testing code. This is not
* the <b>best</b> programming practice, as the series of calls to be invoked is duplicated in two
* places in the code, but it will do for now.
*/
public class Main {
// Set to non-null to write debug info out
@ -35,10 +38,14 @@ public class Main {
// Set to non-null to write dump of control flow graph
public File cfgdumpbase;
/** Symbol for the Main type */
public Symbol.ClassSymbol mainType;
/**
* Symbol for the Main type
*/
public ClassSymbol mainType;
/** List of all type symbols, used by code generator. */
/**
* List of all type symbols, used by code generator.
*/
public List<TypeSymbol> allTypeSymbols;
public void debug(String format, Object... args) {
@ -54,7 +61,9 @@ public class Main {
}
}
/** Parse command line, invoke compile() routine */
/**
* Parse command line, invoke compile() routine
*/
public static void main(String args[]) throws IOException {
Main m = new Main();
@ -62,6 +71,9 @@ public class Main {
if (arg.equals("-d"))
m.debug = new OutputStreamWriter(System.err);
else {
if (m.debug != null)
m.cfgdumpbase = new File(arg);
FileReader fin = new FileReader(arg);
// Parse:
@ -79,9 +91,11 @@ public class Main {
}
}
/** Parses an input stream into an AST
* @throws IOException */
/**
* Parses an input stream into an AST
*
* @throws IOException
*/
public List<ClassDecl> parse(Reader reader) throws IOException {
List<ClassDecl> result = new ArrayList<ClassDecl>();
@ -106,23 +120,33 @@ public class Main {
return result;
}
// TODO: decide how to do/call optimization
public void semanticCheck(List<ClassDecl> astRoots) {
{
new SemanticAnalyzer(this).check(astRoots);
}
new SemanticAnalyzer(this).check(astRoots);
// Build control flow graph:
for (ClassDecl cd : astRoots)
for (MethodDecl md : cd.methods()) {
new CfgBuilder().build(md);
}
CfgDump.toString(astRoots, ".cfg", cfgdumpbase, false);
// Optimize
new AstOptimizer().go(astRoots, allTypeSymbols);
CfgDump.toString(astRoots, ".cfgOPT", cfgdumpbase, false);
}
public void generateCode(List<ClassDecl> astRoots, Writer out) {
{
AstCodeGenerator cg = AstCodeGenerator.createCodeGenerator(this, out);
cg.go(astRoots);
}
CfgCodeGenerator cg = new CfgCodeGenerator(this, out);
cg.go(astRoots);
}
/** Dumps the AST to the debug stream */
/**
* Dumps the AST to the debug stream
*/
private void dumpAst(List<ClassDecl> astRoots) throws IOException {
if (this.debug == null) return;
if (this.debug == null)
return;
this.debug.write(AstDump.toString(astRoots));
}
}

View File

@ -1,14 +0,0 @@
package cd;
/** TAs insert this to mark code that students need to write */
public class ToDoException extends RuntimeException {
private static final long serialVersionUID = 4054810321239901944L;
public ToDoException() {
}
public ToDoException(String message) {
super(message);
}
}

View File

@ -13,7 +13,7 @@ public enum ExitCode {
public final int value;
private ExitCode(int value) {
this.value = value;
private ExitCode(int value) {
this.value = value;
}
}

View File

@ -15,25 +15,33 @@ public class AssemblyEmitter {
this.out = out;
}
/** Creates an constant operand. */
/**
* Creates an constant operand.
*/
static String constant(int i) {
return "$" + i;
}
/** Creates an constant operand with the address of a label. */
/**
* Creates an constant operand with the address of a label.
*/
static String labelAddress(String lbl) {
return "$" + lbl;
}
/** Creates an operand relative to another operand. */
/**
* Creates an operand relative to another operand.
*/
static String registerOffset(int offset, Register reg) {
return String.format("%d(%s)", offset, reg);
}
/** Creates an operand addressing an item in an array */
/**
* Creates an operand addressing an item in an array
*/
static String arrayAddress(Register arrReg, Register idxReg) {
final int offset = Config.SIZEOF_PTR * 2; // one word each in front for
// vptr and length
// vptr and length
final int mul = Config.SIZEOF_PTR; // assume all arrays of 4-byte elem
return String.format("%d(%s,%s,%d)", offset, arrReg, idxReg, mul);
}
@ -90,10 +98,6 @@ public class AssemblyEmitter {
emit(op, constant(src), dest);
}
void emit(String op, int src, String dest) {
emit(op, constant(src), dest);
}
void emit(String op, String dest) {
emitRaw(op + " " + dest);
}
@ -106,10 +110,6 @@ public class AssemblyEmitter {
emit(op, constant(dest));
}
void emit(String op, int src, int dest) {
emit(op, constant(src), constant(dest));
}
void emitMove(Register src, String dest) {
emitMove(src.repr, dest);
}
@ -127,10 +127,6 @@ public class AssemblyEmitter {
emit("movl", src, dest);
}
void emitMove(int src, Register dest) {
emitMove(constant(src), dest);
}
void emitLoad(int srcOffset, Register src, Register dest) {
emitMove(registerOffset(srcOffset, src), dest.repr);
}
@ -143,10 +139,6 @@ public class AssemblyEmitter {
emitMove(src, registerOffset(destOffset, dest));
}
void emitStore(int src, int destOffset, Register dest) {
emitStore(constant(src), destOffset, dest);
}
void emitConstantData(String data) {
emitRaw(String.format("%s %s", Config.DOT_INT, data));
}

View File

@ -4,6 +4,7 @@ public class AssemblyFailedException extends RuntimeException {
private static final long serialVersionUID = -5658502514441032016L;
public final String assemblerOutput;
public AssemblyFailedException(
String assemblerOutput) {
super("Executing assembler failed.\n"

View File

@ -2,42 +2,41 @@ package cd.backend.codegen;
import cd.Config;
import cd.Main;
import cd.backend.ExitCode;
import cd.backend.codegen.RegisterManager.Register;
import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.Expr;
import cd.ir.Ast.MethodDecl;
import cd.ir.Symbol.*;
import java.io.Writer;
import java.util.*;
import java.util.Collections;
import java.util.List;
import static cd.Config.MAIN;
import static cd.Config.*;
import static cd.backend.codegen.AssemblyEmitter.constant;
import static cd.backend.codegen.AssemblyEmitter.registerOffset;
import static cd.backend.codegen.RegisterManager.BASE_REG;
import static cd.backend.codegen.RegisterManager.STACK_REG;
public class AstCodeGenerator {
/** Constant representing the boolean TRUE as integer */
static final int TRUE = 1;
/** Constant representing the boolean FALSE as integer */
static final int FALSE = 0;
/** Size of any variable in assembly
* Primitive variables take up 4 bytes (booleans are integers)
* Reference variables are a 4 byte pointer
*/
static final int VAR_SIZE = 4;
RegsNeededVisitor rnv;
protected RegsNeededVisitor rnv;
ExprGenerator eg;
StmtGenerator sg;
protected ExprGenerator eg;
protected StmtGenerator sg;
protected final Main main;
final AssemblyEmitter emit;
final RegisterManager rm = new RegisterManager();
protected final AssemblyEmitter emit;
protected final RegisterManager rm = new RegisterManager();
protected ExprGeneratorRef egRef;
protected StmtGeneratorRef sgRef;
AstCodeGenerator(Main main, Writer out) {
initMethodData();
main.allTypeSymbols = new ArrayList<>();
this.emit = new AssemblyEmitter(out);
this.main = main;
@ -52,9 +51,10 @@ public class AstCodeGenerator {
}
public static AstCodeGenerator createCodeGenerator(Main main, Writer out) {
return new AstCodeGenerator(main, out);
return new AstCodeGeneratorRef(main, out);
}
/**
* Main method. Causes us to emit x86 assembly corresponding to {@code ast}
* into {@code file}. Throws a {@link RuntimeException} should any I/O error
@ -70,103 +70,693 @@ public class AstCodeGenerator {
* </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)
protected void initMethodData() {
rm.initRegisters();
}
protected void emitMethodSuffix(boolean returnNull) {
if (returnNull)
emit.emit("movl", "$0", Register.EAX);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
}
class AstCodeGeneratorRef extends AstCodeGenerator {
/**
* The address of the this ptr relative to the BP. Note that the this ptr is
* always the first argument. Default offset value is 8 but this can change
* depending on the number of parameters pushed on the stack.
*/
protected int THIS_OFFSET = 8;
/**
* Name of the internal Javali$CheckCast() helper function we generate.
*/
static final String CHECK_CAST = "Javali$CheckCast";
/**
* Name of the internal Javali$CheckNull() helper function we generate.
*/
static final String CHECK_NULL = "Javali$CheckNull";
/**
* Name of the internal Javali$CheckNonZero() helper function we generate.
*/
static final String CHECK_NON_ZERO = "Javali$CheckNonZero";
/**
* Name of the internal Javali$CheckArraySize() helper function we generate.
*/
static final String CHECK_ARRAY_SIZE = "Javali$CheckArraySize";
/**
* Name of the internal Javali$CheckArrayBounds() helper function we
* generate.
*/
static final String CHECK_ARRAY_BOUNDS = "Javali$CheckArrayBounds";
/**
* Name of the internal Javali$Alloc() helper function we generate.
*/
static final String ALLOC = "Javali$Alloc";
/**
* Name of the internal Javali$PrintNewLine() helper function we generate.
*/
static final String PRINT_NEW_LINE = "Javali$PrintNewLine";
/**
* Name of the internal Javali$PrintInteger() helper function we generate.
*/
static final String PRINT_INTEGER = "Javali$PrintInteger";
/**
* Name of the internal Javali$ReadInteger() helper function we generate.
*/
static final String READ_INTEGER = "Javali$ReadInteger";
public AstCodeGeneratorRef(Main main, Writer out) {
super(main, out);
this.egRef = new ExprGeneratorRef(this);
this.eg = this.egRef;
this.sgRef = new StmtGeneratorRef(this);
this.sg = this.sgRef;
}
protected void emitPrefix(List<? extends ClassDecl> astRoots) {
// compute method and field offsets
for (ClassDecl ast : astRoots) {
computeFieldOffsets(ast.sym);
computeVtableOffsets(ast.sym);
}
// emit vtables
for (TypeSymbol ts : main.allTypeSymbols)
emitVtable(ts);
// Emit some useful string constants and static data:
emit.emitRaw(Config.DATA_STR_SECTION);
emit.emitLabel("STR_NL");
emit.emitRaw(Config.DOT_STRING + " \"\\n\"");
emit.emitLabel("STR_D");
emit.emitRaw(Config.DOT_STRING + " \"%d\"");
// Define Object, Object[], int, int[], boolean and boolean[]
List<TypeSymbol> elementTypes = new ArrayList<>();
elementTypes.add(ClassSymbol.objectType);
elementTypes.add(PrimitiveTypeSymbol.intType);
elementTypes.add(PrimitiveTypeSymbol.booleanType);
emit.emitLabel("STR_F");
emit.emitRaw(Config.DOT_STRING + " \"%.5f\"");
emit.emitLabel("SCANF_STR_F");
emit.emitRaw(Config.DOT_STRING + " \"%f\"");
emit.emitRaw(Config.DATA_INT_SECTION);
for (TypeSymbol type : elementTypes) {
// type vtable
emit.emitLabel(Label.type(type));
emit.emitConstantData("0"); // Supertype (null)
// array vtable
emit.emitLabel(Label.type(new ArrayTypeSymbol(type)));
emit.emitConstantData(Label.type(ClassSymbol.objectType)); // Supertype
emit.emitConstantData(Label.type(type)); // Element type
emit.emitRaw(Config.TEXT_SECTION);
// Generate a helper method for checking casts:
// It takes first a vtable and second an object ptr.
{
Register obj = RegisterManager.CALLER_SAVE[0];
Register cls = RegisterManager.CALLER_SAVE[1];
String looplbl = emit.uniqueLabel();
String donelbl = emit.uniqueLabel();
String faillbl = emit.uniqueLabel();
emit.emitCommentSection(CHECK_CAST + " function");
emit.emitLabel(CHECK_CAST);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emitLoad(SIZEOF_PTR * 2, BASE_REG, cls);
emit.emitLoad(SIZEOF_PTR * 3, BASE_REG, obj);
emit.emit("cmpl", constant(0), obj);
emit.emit("je", donelbl); // allow null objects to pass
emit.emitLoad(0, obj, obj); // load vtbl of object
emit.emitLabel(looplbl);
emit.emit("cmpl", obj, cls);
emit.emit("je", donelbl);
emit.emit("cmpl", constant(0), obj);
emit.emit("je", faillbl);
emit.emitLoad(0, obj, obj); // load parent vtable
emit.emit("jmp", looplbl);
emit.emitLabel(faillbl);
emit.emitStore(constant(ExitCode.INVALID_DOWNCAST.value), 0, STACK_REG);
emit.emit("call", Config.EXIT);
emit.emitLabel(donelbl);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Emit the new Main().main() code to start the program:
// Generate a helper method for checking for null ptrs:
{
String oknulllbl = emit.uniqueLabel();
emit.emitCommentSection(CHECK_NULL + " function");
emit.emitLabel(CHECK_NULL);
// 1. Enter TEXT and start the program
emit.emitRaw(Config.TEXT_SECTION);
emit.emit(".globl", MAIN);
emit.emitLabel(MAIN);
// 1.1. Prepare first frame
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
// 2. Create main variable
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG));
emit.emit("jne", oknulllbl);
emit.emitStore(constant(ExitCode.NULL_POINTER.value), 0, STACK_REG);
emit.emit("call", Config.EXIT);
emit.emitLabel(oknulllbl);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for checking that we don't divide by zero:
{
String oknzlbl = emit.uniqueLabel();
emit.emitCommentSection(CHECK_NON_ZERO + " function");
emit.emitLabel(CHECK_NON_ZERO);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG));
emit.emit("jne", oknzlbl);
emit.emitStore(constant(ExitCode.DIVISION_BY_ZERO.value), 0, STACK_REG);
emit.emit("call", Config.EXIT);
emit.emitLabel(oknzlbl);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for checking array size:
{
String okunqlbl = emit.uniqueLabel();
emit.emitCommentSection(CHECK_ARRAY_SIZE + " function");
emit.emitLabel(CHECK_ARRAY_SIZE);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG));
emit.emit("jge", okunqlbl);
emit.emitStore(constant(ExitCode.INVALID_ARRAY_SIZE.value), 0, STACK_REG);
emit.emit("call", Config.EXIT);
emit.emitLabel(okunqlbl);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for checking array bounds:
{
Register arr = RegisterManager.CALLER_SAVE[0];
Register idx = RegisterManager.CALLER_SAVE[1];
String faillbl = emit.uniqueLabel();
emit.emitCommentSection(CHECK_ARRAY_BOUNDS + " function");
emit.emitLabel(CHECK_ARRAY_BOUNDS);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emitLoad(SIZEOF_PTR * 3, BASE_REG, idx);
emit.emitLoad(SIZEOF_PTR * 2, BASE_REG, arr);
emit.emit("cmpl", constant(0), idx); // idx < 0
emit.emit("jl", faillbl);
emit.emit("cmpl", registerOffset(Config.SIZEOF_PTR, arr), idx); // idx >= len
emit.emit("jge", faillbl);
// done
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
// fail
emit.emitLabel(faillbl);
emit.emitStore(constant(ExitCode.INVALID_ARRAY_BOUNDS.value), 0, STACK_REG);
emit.emit("call", Config.EXIT);
}
// Generate a helper method for allocating objects/arrays
{
Register size = RegisterManager.CALLER_SAVE[0];
emit.emitCommentSection(ALLOC + " function");
emit.emitLabel(ALLOC);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emitLoad(8, BASE_REG, size);
emit.emitStore(size, 0, STACK_REG);
emit.emitStore(constant(1), 4, STACK_REG);
emit.emit("call", Config.CALLOC);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for printing a new line
{
emit.emitCommentSection(PRINT_NEW_LINE + " function");
emit.emitLabel(PRINT_NEW_LINE);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emitStore("$STR_NL", 0, STACK_REG);
emit.emit("call", Config.PRINTF);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for printing an integer
{
Register temp = RegisterManager.CALLER_SAVE[0];
emit.emitCommentSection(PRINT_INTEGER + " function");
emit.emitLabel(PRINT_INTEGER);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emitLoad(8, BASE_REG, temp);
emit.emitStore(temp, 4, STACK_REG);
emit.emitStore("$STR_D", 0, STACK_REG);
emit.emit("call", Config.PRINTF);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for reading an integer
{
Register number = RegisterManager.CALLER_SAVE[0];
emit.emitCommentSection(READ_INTEGER + " function");
emit.emitLabel(READ_INTEGER);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emit("leal", registerOffset(8, STACK_REG), number);
emit.emitStore(number, 4, STACK_REG);
emit.emitStore("$STR_D", 0, STACK_REG);
emit.emit("call", SCANF);
emit.emitLoad(8, STACK_REG, Register.EAX);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate AST for main() method:
// new Main().main()
Ast.NewObject newMain = new Ast.NewObject("Main");
newMain.type = main.mainType;
Register mainLocation = eg.visit(newMain, null);
// 3. Call main()
emit.emit("push", mainLocation);
for (ClassSymbol sym = main.mainType; sym != ClassSymbol.objectType; sym = sym.superClass)
if (sym.methods.getOrDefault("main", null) != null) {
emit.emit("call", Label.method(sym, sym.methods.get("main")));
break;
}
emitMethodSuffix(true);
Ast.MethodCallExpr mce = new Ast.MethodCallExpr(newMain, "main", Collections.<Expr>emptyList());
Ast.MethodCall callMain = new Ast.MethodCall(mce);
mce.sym = main.mainType.getMethod("main");
// Emit the main() method:
// new Main().main();
emit.emitCommentSection("main() function");
emit.emitRaw(".globl " + MAIN);
emit.emitLabel(MAIN);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", -16, STACK_REG);
sg.gen(callMain);
emit.emit("movl", constant(ExitCode.OK.value), Register.EAX); // normal termination:
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
void initMethodData() {
rm.initRegisters();
@Override
public void go(List<? extends ClassDecl> astRoots) {
emitPrefix(astRoots);
super.go(astRoots);
}
/**
* Computes the vtable offset for each method defined in the class
* {@code sym}.
*/
protected int computeVtableOffsets(ClassSymbol sym) {
if (sym == null)
return 0;
if (sym.totalMethods != -1)
return sym.totalMethods;
int index = computeVtableOffsets(sym.superClass);
for (MethodSymbol ms : sym.methods.values()) {
assert ms.vtableIndex == -1;
if (ms.overrides != null)
ms.vtableIndex = ms.overrides.vtableIndex;
else
ms.vtableIndex = index++;
}
sym.totalMethods = index;
return index;
}
/**
* Computes the offset for each field.
*/
protected int computeFieldOffsets(ClassSymbol sym) {
if (sym == null)
return 0;
if (sym.totalFields != -1)
return sym.totalFields;
int index = computeFieldOffsets(sym.superClass);
for (VariableSymbol fs : sym.fields.values()) {
assert fs.offset == -1;
// compute offset in bytes; note that 0 is the vtable
fs.offset = (index * SIZEOF_PTR) + SIZEOF_PTR;
index++;
}
sym.totalFields = index;
sym.sizeof = (sym.totalFields + 1) * Config.SIZEOF_PTR;
return index;
}
private void collectVtable(MethodSymbol[] vtable, ClassSymbol sym) {
if (sym.superClass != null)
collectVtable(vtable, sym.superClass);
for (MethodSymbol ms : sym.methods.values())
vtable[ms.vtableIndex] = ms;
}
protected void emitVtable(TypeSymbol ts) {
if (ts instanceof ClassSymbol) {
ClassSymbol cs = (ClassSymbol) ts;
// Collect the vtable:
MethodSymbol[] vtable = new MethodSymbol[cs.totalMethods];
collectVtable(vtable, cs);
// Emit vtable for this class:
emit.emitLabel(vtable(cs));
if (cs.superClass != null)
emit.emitConstantData(vtable(cs.superClass));
else
emit.emitConstantData("0");
for (int i = 0; i < cs.totalMethods; i++)
emit.emitConstantData(methodLabel(vtable[i]));
} else if (ts instanceof ArrayTypeSymbol) {
ArrayTypeSymbol as = (ArrayTypeSymbol) ts;
emit.emitLabel(vtable(as));
emit.emitConstantData(vtable(ClassSymbol.objectType));
}
}
protected String vtable(TypeSymbol ts) {
if (ts instanceof ClassSymbol) {
return "vtable_" + ((ClassSymbol) ts).name;
} else if (ts instanceof ArrayTypeSymbol) {
return "vtablearr_" + ((ArrayTypeSymbol) ts).elementType.name;
} else {
throw new RuntimeException("No vtable for " + ts.name);
}
}
void emitMethodSuffix(boolean returnNull) {
@Override
protected void initMethodData() {
THIS_OFFSET = 8;
bytes = 0;
super.initMethodData();
}
protected int padding(int numberOfParameters) {
int padding = (bytes + numberOfParameters * Config.SIZEOF_PTR + 15) & 0xFFFFFFF0;
return padding - bytes - numberOfParameters * Config.SIZEOF_PTR;
}
protected void push(int padding) {
if (padding > 0) {
emit.emit("sub", padding, STACK_REG);
bytes += padding;
}
}
protected void pop(int padding) {
if (padding > 0) {
emit.emit("add", padding, STACK_REG);
bytes -= padding;
}
assert bytes >= 0;
}
protected void push(String reg) {
emit.emit("push", reg);
bytes += Config.SIZEOF_PTR;
}
protected void pop(String reg) {
emit.emit("pop", reg);
bytes -= Config.SIZEOF_PTR;
assert bytes >= 0;
}
protected void restoreCalleeSaveRegs() {
for (int reg = RegisterManager.CALLEE_SAVE.length - 1; reg >= 0; reg--) {
emit.emit("pop", RegisterManager.CALLEE_SAVE[reg]);
}
}
protected void storeCalleeSaveRegs() {
bytes = 0;
for (int reg = 0; reg < RegisterManager.CALLEE_SAVE.length; reg++) {
emit.emit("push", RegisterManager.CALLEE_SAVE[reg]);
bytes += Config.SIZEOF_PTR;
}
}
protected void restoreCallerSaveRegs(Register res) {
for (int reg = RegisterManager.CALLER_SAVE.length - 1; reg >= 0; reg--) {
if (!rm.isInUse(RegisterManager.CALLER_SAVE[reg]))
continue; // not in use
if (RegisterManager.CALLER_SAVE[reg].equals(res))
continue; // contains our result
pop(RegisterManager.CALLER_SAVE[reg].repr);
}
}
protected void storeCallerSaveRegs(Register res) {
for (int reg = 0; reg < RegisterManager.CALLER_SAVE.length; reg++) {
if (!rm.isInUse(RegisterManager.CALLER_SAVE[reg]))
continue; // not in use
if (RegisterManager.CALLER_SAVE[reg].equals(res))
continue; // will contain our result
push(RegisterManager.CALLER_SAVE[reg].repr);
}
}
protected int emitCallPrefix(Register res, int numberOfParameters) {
storeCallerSaveRegs(res);
int padding = padding(numberOfParameters);
push(padding);
return padding;
}
protected void emitCallSuffix(Register res, int numberOfParameters,
int padding) {
pop(numberOfParameters * Config.SIZEOF_PTR + padding);
if (res != null) {
emit.emitMove(Register.EAX, res);
}
restoreCallerSaveRegs(res);
}
/**
* Generates code which evaluates {@code ast} and branches to {@code lbl} if
* the value generated for {@code ast} is false.
*/
protected void genJumpIfFalse(Expr ast, String lbl) {
// A better way to implement this would be with a separate
// visitor.
Register reg = eg.gen(ast);
emit.emit("cmpl", "$0", reg);
emit.emit("je", lbl);
rm.releaseRegister(reg);
}
/**
* Used to store the temporaries. We grow our stack dynamically, we allocate
* "temporary" values on this stack during method execution. Values can be
* stored and retrieved using {@link #push(String)} and {@link #pop(String)}
* , which use the program stack.
*/
protected int bytes = 0;
protected String methodLabel(MethodSymbol msym) {
return "meth_" + msym.owner.name + "_" + msym.name;
}
protected void emitMethodPrefix(MethodDecl ast) {
// Emit the label for the method:
emit.emitRaw(Config.TEXT_SECTION);
emit.emitCommentSection(String.format("Method %s.%s", ast.sym.owner.name,
ast.name));
emit.emitRaw(".globl " + methodLabel(ast.sym));
emit.emitLabel(methodLabel(ast.sym));
// Compute the size and layout of the stack frame. Our
// frame looks like (the numbers are relative to our ebp):
//
// (caller's locals)
// (padding)
// arg 0 (this ptr)
// ...
// 12 arg N - 1
// 8 arg N
// 4 linkage ptr (return address)
// 0 saved ebp
// -4 locals
// (callee's arguments + temporaries)
//
// We allocate on the stack during the course of
// a function call using push(...) and pop(...) instructions.
//
// Stack slots fall into several
// categories:
// - "Linkage": overhead for function calls.
// This includes the return address and saved ebp.
// - locals: these store the value of user-declared local
// variables.
// - temporaries: these are stack slots used to store
// values during expression evaluation when we run out
// of registers, saving caller-saved registers, and
// other miscellaneous purposes.
// - padding: only there to ensure the stack size is a multiple
// of 16.
// - arguments: values we will pass to functions being
// invoked.
//
// We calculate all address relative to the base pointer.
// Initialize method-specific data
initMethodData();
// Assign parameter offsets:
// As shown above, these start from 8.
// Being able to evaluate parameters like in Java
// with left-to-right evaluation order they result
// on the stack in reversed order.
// The "this" parameter is the first pushed on the stack
// thus receiving higher offset.
int paramOffset = Config.SIZEOF_PTR * 2;
for (int i = ast.sym.parameters.size() - 1; i >= 0; i--) {
final VariableSymbol param = ast.sym.parameters.get(i);
assert param.offset == -1;
param.offset = paramOffset;
paramOffset += Config.SIZEOF_PTR;
}
THIS_OFFSET = paramOffset;
paramOffset += Config.SIZEOF_PTR;
// First few slots are reserved for caller save regs:
int localSlot = RegisterManager.CALLER_SAVE.length * RegisterManager.SIZEOF_REG;
// Assign local variable offsets:
emit.emitComment(String.format("%-10s Offset", "Variable"));
for (VariableSymbol local : ast.sym.locals.values()) {
assert local.offset == -1;
local.offset = -localSlot;
localSlot += Config.SIZEOF_PTR;
emit.emitComment(String.format("%-10s %d", local, local.offset));
}
// Round up stack size to make it a multiple of 16.
// The actual amount passed to the enter instruction is 8
// less, however, because it wants to know the amount
// in addition to the linkage ptr and saved ebp.
int implicit = Config.SIZEOF_PTR * 2;
int stackSize = (implicit + localSlot + 15) & 0xFFFFFFF0;
stackSize -= implicit;
emit.emitComment(String.format("implicit=%d localSlot=%d sum=%d", implicit,
localSlot, implicit + localSlot));
// emit.emitRaw(String.format("enter $%d, $0", stackSize));
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(stackSize), STACK_REG);
emit.emit("and", -16, STACK_REG);
storeCalleeSaveRegs();
// zero-initialize locals
for (VariableSymbol local : ast.sym.locals.values()) {
emit.emitMove(constant(0), registerOffset(local.offset, BASE_REG));
}
}
@Override
protected void emitMethodSuffix(boolean returnNull) {
if (returnNull)
emit.emit("movl", 0, Register.EAX);
emit.emitRaw("leave");
emit.emit("movl", "$0", Register.EAX);
restoreCalleeSaveRegs();
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
static class Label {
static String type(TypeSymbol symbol) {
if (symbol instanceof ClassSymbol)
return String.format("type_%s", symbol);
else if (symbol instanceof ArrayTypeSymbol)
return String.format("array_%s", ((ArrayTypeSymbol) symbol).elementType);
else if (symbol instanceof PrimitiveTypeSymbol)
return String.format("primive_%s", symbol);
throw new RuntimeException("Unimplemented type symbol");
}
static String method(ClassSymbol classSymbol, MethodSymbol methodSymbol) {
return String.format("method_%s_%s", classSymbol.name, methodSymbol.name);
}
static String returnMethod(ClassSymbol classSymbol, MethodSymbol methodSymbol) {
return String.format("return_%s", method(classSymbol, methodSymbol));
}
}
}
class AstCodeGeneratorNop90 extends AstCodeGeneratorRef {
public AstCodeGeneratorNop90(Main main, Writer out, CfgCodeGenerator cfgCg) {
super(main, out);
this.egRef = new ExprGeneratorNop90(this, cfgCg);
this.eg = this.egRef;
this.sgRef = new StmtGeneratorNop90(this, cfgCg);
this.sg = this.sgRef;
}
}

View File

@ -0,0 +1,130 @@
package cd.backend.codegen;
import cd.Main;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.MethodDecl;
import cd.ir.Ast.Stmt;
import cd.ir.Ast;
import cd.ir.AstVisitor;
import cd.ir.BasicBlock;
import cd.ir.ControlFlowGraph;
import cd.ir.Symbol.PrimitiveTypeSymbol;
import cd.ir.Symbol.TypeSymbol;
import cd.ir.Symbol.VariableSymbol;
import cd.transform.analysis.ArraySizeAnalysis;
import cd.transform.analysis.AssignmentAnalysis;
import cd.transform.analysis.DynamicTypeAnalysis;
import cd.transform.analysis.MaybeC;
import cd.transform.analysis.NullAnalysis;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CfgCodeGenerator {
public final Main main;
private final AstCodeGeneratorRef cg;
public final CompileTimeChecks check;
protected Map<VariableSymbol, MaybeC<Integer>> arraySizeState;
protected Map<VariableSymbol, MaybeC<Boolean>> nullState;
protected Map<VariableSymbol, MaybeC<TypeSymbol>> dynamicTypeState;
//protected Map<VariableSymbol, MaybeC<List<Ast.Assign>>> unusedAssignmentsState;
public CfgCodeGenerator(Main main, Writer out) {
this.main = main;
cg = new AstCodeGeneratorNop90(main, out, this);
check = new CompileTimeChecks(cg, this);
}
public void go(List<? extends ClassDecl> astRoots) {
cg.emitPrefix(astRoots);
for (ClassDecl cdecl : astRoots)
new CfgStmtVisitor().visit(cdecl, null);
}
private class CfgStmtVisitor extends AstVisitor<Void, Void> {
@Override
public Void classDecl(ClassDecl ast, Void arg) {
cg.emit.emitCommentSection("Class " + ast.name);
cg.emit.increaseIndent("");
super.classDecl(ast, arg);
cg.emit.decreaseIndent();
return null;
}
@Override
public Void methodDecl(MethodDecl ast, Void arg) {
cg.emitMethodPrefix(ast);
ControlFlowGraph cfg = ast.cfg;
assert cfg != null;
Map<BasicBlock, String> labels = new HashMap<BasicBlock, String>();
for (BasicBlock blk : cfg.allBlocks)
labels.put(blk, cg.emit.uniqueLabel());
String exitLabel = cg.emit.uniqueLabel();
cg.emit.emit("jmp", labels.get(cfg.start));
// Run analysis here to have the relevant results to this cfg object
ArraySizeAnalysis arraySizeAnalysis = new ArraySizeAnalysis(cfg);
NullAnalysis nullAnalysis = new NullAnalysis(cfg);
DynamicTypeAnalysis dynamicTypeAnalysis = new DynamicTypeAnalysis(cfg);
//AssignmentAnalysis assignmentAnalysis = new AssignmentAnalysis(cfg);
for (BasicBlock blk : cfg.allBlocks) {
arraySizeState = arraySizeAnalysis.inStateOf(blk);
nullState = nullAnalysis.inStateOf(blk);
dynamicTypeState = dynamicTypeAnalysis.inStateOf(blk);
//unusedAssignmentsState = assignmentAnalysis.inStateOf(blk);
assert nullState != null;
assert arraySizeState != null;
assert dynamicTypeState != null;
//assert unusedAssignmentsState != null;
cg.emit.emitCommentSection("Basic block " + blk.index);
cg.emit.emitLabel(labels.get(blk));
for (Stmt stmt : blk.stmts) {
cg.sg.gen(stmt);
//assignmentAnalysis.transferStmt(stmt, unusedAssignmentsState);
arraySizeAnalysis.transferStmt(stmt, arraySizeState);
nullAnalysis.transferStmt(stmt, nullState);
dynamicTypeAnalysis.transferStmt(stmt, dynamicTypeState);
}
if (blk == cfg.end) {
cg.emit.emitComment(String.format("Return"));
assert blk.successors.size() == 0;
cg.emit.emit("jmp", exitLabel);
} else if (blk.condition != null) {
assert blk.successors.size() == 2;
cg.emit.emitComment(String.format(
"Exit to block %d if true, block %d if false",
blk.trueSuccessor().index, blk.falseSuccessor().index));
cg.genJumpIfFalse(blk.condition, labels.get(blk.falseSuccessor()));
cg.emit.emit("jmp", labels.get(blk.trueSuccessor()));
} else {
cg.emit.emitComment(String.format(
"Exit to block %d", blk.successors.get(0).index));
assert blk.successors.size() == 1;
cg.emit.emit("jmp", labels.get(blk.successors.get(0)));
}
}
cg.emit.emitLabel(exitLabel);
if (ast.sym.returnType.equals(PrimitiveTypeSymbol.voidType))
cg.emitMethodSuffix(true);
else
cg.emitMethodSuffix(true);
return null;
}
}
}

View File

@ -0,0 +1,106 @@
package cd.backend.codegen;
import cd.Config;
import cd.backend.ExitCode;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.CompileTimeEvaluator;
import cd.ir.Symbol.VariableSymbol;
import cd.transform.analysis.MaybeC;
import java.util.Optional;
import static cd.backend.codegen.AssemblyEmitter.constant;
import static cd.backend.codegen.RegisterManager.STACK_REG;
/**
* Class with simple static checks mimicking the runtime system with the functions to check
* possible errors at runtime. The purpose is to skip unnecessary calls to those functions
*/
public class CompileTimeChecks {
protected final CfgCodeGenerator cfgCg;
protected final AstCodeGeneratorRef cgRef;
protected CompileTimeEvaluator cte;
public CompileTimeChecks(AstCodeGeneratorRef cg, CfgCodeGenerator cfgCodeGenerator) {
this.cfgCg = cfgCodeGenerator;
this.cgRef = cg;
cte = new CompileTimeEvaluator();
}
/**
* Convenience method that extracts the VariableSymbol from a Var or Field node.
* <br/> If the node is not one of those, the node must be a Index
*/
private VariableSymbol symbolOf(Expr expr) {
if (expr instanceof Var)
return ((Var) expr).sym;
if (expr instanceof Field)
return ((Field) expr).sym;
// assert expr instanceof Index // only for local checks
return null;
}
/** Returns whether a NewArray expression needs to check dynamically for the
* validity of the index (>=0). In case that it is known but invalid, a call
* to exit is done with the appropriate error code.
*/
public boolean checkArraySize(Ast.NewArray expr) {
Optional<Integer> arraySize = cte.calc(expr.arg());
if (!arraySize.isPresent()) {
return true;
}
int arraySizeValue = arraySize.get();
if (arraySizeValue < 0) {
cgRef.emit.emitStore(constant(ExitCode.INVALID_ARRAY_SIZE.value), 0, STACK_REG);
cgRef.emit.emit("call", Config.EXIT);
return false;
}
return false;
}
/** Returns whether a run-time check for the bounds of an array index is necessary.
* <br/> A check is only necessary if the index expression cannot be evaluated in
* compile time or the size of the array is unknown.
* <br/> If the value is known and it is invalid, an unconditional call to exit is
* performed with the appropriate exit code
*/
public boolean checkArrayBound(Index expr) {
Optional<Integer> index = cte.calc(expr.right());
if (!index.isPresent()) {
return true;
}
VariableSymbol symbol = symbolOf(expr.left());
if (!cfgCg.arraySizeState.containsKey(symbol)) {
return true;
}
int indexValue = index.get();
MaybeC<Integer> bound = cfgCg.arraySizeState.get(symbol);
if (bound.isNotConstant()) {
return true;
}
int boundValue = bound.getValue();
if (indexValue < 0 || indexValue > (boundValue - 1)) {
cgRef.emit.emitStore(constant(ExitCode.INVALID_ARRAY_BOUNDS.value), 0, STACK_REG);
cgRef.emit.emit("call", Config.EXIT);
}
return false;
}
/**
* Returns true only when it is impossible for the expression (Field or Var) to be null.
* <br/> Currently doesn't check indexes.
*/
public boolean isNotNull(Expr expr) {
if (expr instanceof ThisRef) {
return false;
} else {
VariableSymbol symbol = symbolOf(expr);
MaybeC<Boolean> isNull = cfgCg.nullState.get(symbol);
return isNull != null && isNull.isConstant() && !isNull.getValue();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +0,0 @@
package cd.backend.codegen;
import cd.backend.ExitCode;
import cd.backend.codegen.RegisterManager.Register;
public class Interrupts {
protected static final int INTERRUPT_EXIT = 1;
/**
* Generates a exit interrupt with the code provided
* @param cg AstCodeGenerator to print instructions
* @param exitCode Number to use as exit code (can use constants in this class)
*/
protected static void exit(AstCodeGenerator cg, ExitCode exitCode) {
cg.emit.emitMove(AssemblyEmitter.constant(INTERRUPT_EXIT), Register.EAX);
cg.emit.emitMove(AssemblyEmitter.constant(exitCode.value), Register.EBX);
cg.emit.emit("int", 0x80);
}
}

View File

@ -1,42 +0,0 @@
package cd.backend.codegen;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.Symbol.MethodSymbol;
public class Location {
private ClassSymbol classSymbol;
private MethodSymbol methodSymbol = null;
private boolean obtainReference = false;
public Location (ClassSymbol sym) {
classSymbol = sym;
}
public ClassSymbol classSym() {
return classSymbol;
}
public MethodSymbol methodSym() {
assert methodSymbol != null;
return methodSymbol;
}
public void enterMethod(MethodSymbol sym) {
methodSymbol = sym;
}
public void leaveMethod() {
methodSymbol = null;
}
public boolean isObtainReference() {
boolean aux = obtainReference;
obtainReference = false;
return aux;
}
public Location obtainReference() {
obtainReference = true;
return this;
}
}

View File

@ -19,7 +19,7 @@ public class RegisterManager {
// list of general purpose registers
public static final Register GPR[] = new Register[]{Register.EAX, Register.EBX,
Register.ECX, Register.EDX, Register.ESI, Register.EDI};
Register.ECX, Register.EDX, Register.ESI, Register.EDI};
// special purpose registers
public static final Register BASE_REG = Register.EBP;
@ -99,18 +99,11 @@ public class RegisterManager {
return registers.remove(last);
}
public void useRegister(Register reg) {
assert registers.contains(reg);
assert reg != null;
registers.remove(reg);
}
/**
* marks a currently used register as free
*/
public void releaseRegister(Register reg) {
assert !registers.contains(reg);
assert reg != null;
registers.add(reg);
}

View File

@ -12,14 +12,15 @@ import static java.lang.Math.min;
/**
* Determines the maximum number of registers
* required to execute one subtree. */
* required to execute one subtree.
*/
public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public int calc(Ast ast) {
return visit(ast, null);
}
private Map<Ast,Integer> memo = new HashMap<Ast, Integer>();
private Map<Ast, Integer> memo = new HashMap<Ast, Integer>();
/**
* Override visit() so as to memorize the results and avoid
@ -54,8 +55,8 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public Integer binaryOp(BinaryOp ast, Void arg) {
int left = calc(ast.left());
int right = calc(ast.right());
int ifLeftFirst = max(left, right+1);
int ifRightFirst = max(left+1, right);
int ifLeftFirst = max(left, right + 1);
int ifRightFirst = max(left + 1, right);
int overall = min(ifLeftFirst, ifRightFirst);
return overall;
}

View File

@ -1,34 +1,31 @@
package cd.backend.codegen;
import cd.Config;
import cd.backend.ExitCode;
import cd.backend.codegen.AstCodeGenerator.Label;
import cd.backend.codegen.RegisterManager.Register;
import cd.frontend.semantic.ReturnCheckerVisitor;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.AstVisitor;
import cd.ir.Symbol;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.CompileTimeEvaluator;
import cd.ir.ExprVisitor;
import cd.ir.Symbol.MethodSymbol;
import cd.ir.Symbol.PrimitiveTypeSymbol;
import cd.util.Pair;
import cd.util.debug.AstOneLine;
import java.util.*;
import java.util.List;
import static cd.backend.codegen.AstCodeGenerator.VAR_SIZE;
import static cd.backend.codegen.AstCodeGenerator.TRUE;
import static cd.backend.codegen.AssemblyEmitter.arrayAddress;
import static cd.backend.codegen.RegisterManager.BASE_REG;
import static cd.backend.codegen.RegisterManager.STACK_REG;
/**
* Generates code to process statements and declarations.
*/
class StmtGenerator extends AstVisitor<Register,Location> {
class StmtGenerator extends AstVisitor<Register, Void> {
protected final AstCodeGenerator cg;
StmtGenerator(AstCodeGenerator astCodeGenerator) {
cg = astCodeGenerator;
}
public void gen(Ast ast) {
@ -36,253 +33,337 @@ class StmtGenerator extends AstVisitor<Register,Location> {
}
@Override
public Register visit(Ast ast, Location arg) {
public Register visit(Ast ast, Void arg) {
try {
cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast));
if (cg.rnv.calc(ast) > cg.rm.availableRegisters()) {
Deque<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);
return super.visit(ast, arg);
} finally {
cg.emit.decreaseIndent();
}
}
@Override
public Register methodCall(MethodCall ast, Location arg) {
Register result = cg.eg.visit(ast.getMethodCallExpr(), arg);
if (result != null)
cg.rm.releaseRegister(result);
return null;
}
public Register methodCall(MethodSymbol sym, List<Expr> allArguments) {
throw new RuntimeException("Not required");
}
/**
* vtable structure for a class type: pointer to superclass, and methods, each with 2 entries, name's hashCode and
* pointer to method execution
*/
}
/*
* StmtGenerator with the reference solution
*/
class StmtGeneratorRef extends StmtGenerator {
/* cg and cgRef are the same instance. cgRef simply
* provides a wider interface */
protected final AstCodeGeneratorRef cgRef;
StmtGeneratorRef(AstCodeGeneratorRef astCodeGenerator) {
super(astCodeGenerator);
this.cgRef = astCodeGenerator;
}
@Override
public Register classDecl(ClassDecl ast, Location arg) {
cg.emit.emitRaw(Config.DATA_INT_SECTION);
// Emit vtable for class
cg.emit.emitLabel(Label.type(ast.sym)); // Label
cg.emit.emitConstantData(Label.type(ast.sym.superClass)); // Superclass
// Methods (don't write those that are overridden twice)
Set<String> generated = new HashSet<>();
ClassSymbol currSym = ast.sym;
while (currSym != ClassSymbol.objectType) {
ClassSymbol finalCurrSym = currSym;
currSym.methods.values().forEach(o -> {
if (!generated.add(o.name)) return;
cg.emit.emitConstantData(String.valueOf(o.name.hashCode()));
cg.emit.emitConstantData(Label.method(finalCurrSym, o));
});
currSym = currSym.superClass;
public Register methodCall(MethodSymbol mthSymbol, List<Expr> allArgs) {
// Push the arguments and the method prefix (caller save register,
// and padding) onto the stack.
// Note that the space for the arguments is not already reserved,
// so we just push them in the Java left-to-right order.
//
// After each iteration of the following loop, reg holds the
// register used for the previous argument.
int padding = cgRef.emitCallPrefix(null, allArgs.size());
Register reg = null;
for (int i = 0; i < allArgs.size(); i++) {
if (reg != null) {
cgRef.rm.releaseRegister(reg);
}
reg = cgRef.eg.gen(allArgs.get(i));
cgRef.push(reg.repr);
}
// End of class vtable
// Array vtable
boolean found = false;
for (Symbol.TypeSymbol type : cg.main.allTypeSymbols) {
if (type instanceof Symbol.ArrayTypeSymbol) {
Symbol.ArrayTypeSymbol aType = (Symbol.ArrayTypeSymbol) type;
if (aType.elementType == ast.sym) {
cg.emit.emitLabel(Label.type(aType)); // Label
found = true;
break;
}
// Since "this" is the first parameter that push
// we have to get it back to resolve the method call
cgRef.emit.emitComment("Load \"this\" pointer");
cgRef.emit.emitLoad((allArgs.size() - 1) * Config.SIZEOF_PTR, STACK_REG, reg);
// Check for a null receiver
int cnPadding = cgRef.emitCallPrefix(null, 1);
cgRef.push(reg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL);
cgRef.emitCallSuffix(null, 1, cnPadding);
// Load the address of the method to call into "reg"
// and call it indirectly.
cgRef.emit.emitLoad(0, reg, reg);
int mthdoffset = 4 + mthSymbol.vtableIndex * Config.SIZEOF_PTR;
cgRef.emit.emitLoad(mthdoffset, reg, reg);
cgRef.emit.emit("call", "*" + reg);
cgRef.emitCallSuffix(reg, allArgs.size(), padding);
if (mthSymbol.returnType == PrimitiveTypeSymbol.voidType) {
cgRef.rm.releaseRegister(reg);
return null;
}
return reg;
}
@Override
public Register methodCall(MethodCall ast, Void dummy) {
Register reg = cgRef.eg.gen(ast.getMethodCallExpr());
if (reg != null)
cgRef.rm.releaseRegister(reg);
return reg;
}
@Override
public Register classDecl(ClassDecl ast, Void arg) {
// Emit each method:
cgRef.emit.emitCommentSection("Class " + ast.name);
return visitChildren(ast, arg);
}
@Override
public Register methodDecl(MethodDecl ast, Void arg) {
cgRef.emitMethodPrefix(ast);
gen(ast.body());
cgRef.emitMethodSuffix(false);
return null;
}
@Override
public Register ifElse(IfElse ast, Void arg) {
String falseLbl = cgRef.emit.uniqueLabel();
String doneLbl = cgRef.emit.uniqueLabel();
cgRef.genJumpIfFalse(ast.condition(), falseLbl);
gen(ast.then());
cgRef.emit.emit("jmp", doneLbl);
cgRef.emit.emitLabel(falseLbl);
gen(ast.otherwise());
cgRef.emit.emitLabel(doneLbl);
return null;
}
@Override
public Register whileLoop(WhileLoop ast, Void arg) {
String nextLbl = cgRef.emit.uniqueLabel();
String doneLbl = cgRef.emit.uniqueLabel();
cgRef.emit.emitLabel(nextLbl);
cgRef.genJumpIfFalse(ast.condition(), doneLbl);
gen(ast.body());
cgRef.emit.emit("jmp", nextLbl);
cgRef.emit.emitLabel(doneLbl);
return null;
}
@Override
public Register assign(Assign ast, Void arg) {
class AssignVisitor extends ExprVisitor<Void, Expr> {
@Override
public Void var(Var ast, Expr right) {
final Register rhsReg = cgRef.eg.gen(right);
cgRef.emit.emitStore(rhsReg, ast.sym.offset, BASE_REG);
cgRef.rm.releaseRegister(rhsReg);
return null;
}
@Override
public Void field(Field ast, Expr right) {
final Register rhsReg = cgRef.eg.gen(right);
Pair<Register> regs = cgRef.egRef.genPushing(rhsReg, ast.arg());
int padding = cgRef.emitCallPrefix(null, 1);
cgRef.push(regs.b.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL);
cgRef.emitCallSuffix(null, 1, padding);
cgRef.emit.emitStore(regs.a, ast.sym.offset, regs.b);
cgRef.rm.releaseRegister(regs.b);
cgRef.rm.releaseRegister(regs.a);
return null;
}
@Override
public Void index(Index ast, Expr right) {
Register rhsReg = cgRef.egRef.gen(right);
Pair<Register> regs = cgRef.egRef.genPushing(rhsReg, ast.left());
rhsReg = regs.a;
Register arrReg = regs.b;
int padding = cgRef.emitCallPrefix(null, 1);
cgRef.push(arrReg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL);
cgRef.emitCallSuffix(null, 1, padding);
regs = cgRef.egRef.genPushing(arrReg, ast.right());
arrReg = regs.a;
Register idxReg = regs.b;
// Check array bounds
padding = cgRef.emitCallPrefix(null, 2);
cgRef.push(idxReg.repr);
cgRef.push(arrReg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_BOUNDS);
cgRef.emitCallSuffix(null, 2, padding);
cgRef.emit.emitMove(rhsReg, arrayAddress(arrReg, idxReg));
cgRef.rm.releaseRegister(arrReg);
cgRef.rm.releaseRegister(idxReg);
cgRef.rm.releaseRegister(rhsReg);
return null;
}
@Override
protected Void dfltExpr(Expr ast, Expr arg) {
throw new RuntimeException("Store to unexpected lvalue " + ast);
}
}
new AssignVisitor().visit(ast.left(), ast.right());
return null;
}
@Override
public Register builtInWrite(BuiltInWrite ast, Void arg) {
Register reg = cgRef.eg.gen(ast.arg());
int padding = cgRef.emitCallPrefix(null, 1);
cgRef.push(reg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.PRINT_INTEGER);
cgRef.emitCallSuffix(null, 1, padding);
cgRef.rm.releaseRegister(reg);
return null;
}
@Override
public Register builtInWriteln(BuiltInWriteln ast, Void arg) {
int padding = cgRef.emitCallPrefix(null, 0);
cgRef.emit.emit("call", AstCodeGeneratorRef.PRINT_NEW_LINE);
cgRef.emitCallSuffix(null, 0, padding);
return null;
}
@Override
public Register returnStmt(ReturnStmt ast, Void arg) {
if (ast.arg() != null) {
Register reg = cgRef.eg.gen(ast.arg());
cgRef.emit.emitMove(reg, "%eax");
cgRef.emitMethodSuffix(false);
cgRef.rm.releaseRegister(reg);
} else {
cgRef.emitMethodSuffix(true); // no return value -- return NULL as
// a default (required for main())
}
return null;
}
}
class StmtGeneratorNop90 extends StmtGeneratorRef {
protected final CfgCodeGenerator cfgCg;
protected CompileTimeEvaluator cte;
StmtGeneratorNop90(AstCodeGeneratorNop90 astCodeGenerator, CfgCodeGenerator cfgCodeGenerator) {
super(astCodeGenerator);
this.cfgCg = cfgCodeGenerator;
cte = new CompileTimeEvaluator();
}
@Override
public Register assign(Assign ast, Void arg) {
/*
if (ast.left() instanceof Ast.Var) {
Ast.Var var = (Ast.Var) ast.left();
VariableSymbol sym = var.sym;
MaybeC<List<Assign>> maybeStateList = cfgCg.unusedAssignmentsState.get(sym);
List<Ast.Assign> stateList = maybeStateList.getValue();
if (stateList.contains(ast)) {
return null;
}
}
if (!found)
throw new RuntimeException("The array type could not be found");
cg.emit.emitConstantData(Label.type(ClassSymbol.objectType)); // Supertype
cg.emit.emitConstantData(Label.type(ast.sym)); // Type of elements
// End of array vtable
cg.emit.emitRaw(Config.TEXT_SECTION);
*/
class AssignVisitor extends ExprVisitor<Void, Expr> {
// Method bodies
for (Ast method : ast.methods())
visit(method, new Location(ast.sym));
@Override
public Void var(Var ast, Expr right) {
final Register rhsReg = cgRef.eg.gen(right);
cgRef.emit.emitStore(rhsReg, ast.sym.offset, BASE_REG);
cgRef.rm.releaseRegister(rhsReg);
return null;
}
return null;
}
@Override
public Void field(Field ast, Expr right) {
final Register rhsReg = cgRef.eg.gen(right);
Pair<Register> regs = cgRef.egRef.genPushing(rhsReg, ast.arg());
if (!cfgCg.check.isNotNull(ast.arg())) {
int padding = cgRef.emitCallPrefix(null, 1);
cgRef.push(regs.b.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL);
cgRef.emitCallSuffix(null, 1, padding);
}
cgRef.emit.emitStore(regs.a, ast.sym.offset, regs.b);
cgRef.rm.releaseRegister(regs.b);
cgRef.rm.releaseRegister(regs.a);
return null;
}
@Override
public Register methodDecl(MethodDecl ast, Location arg) {
// Bookkeeping for framework
arg.enterMethod(ast.sym);
cg.initMethodData();
@Override
public Void index(Index ast, Expr right) {
boolean isNotNull = cfgCg.check.isNotNull(ast.left());
boolean emitBoundCheck = cfgCg.check.checkArrayBound(ast);
Register rhsReg = cgRef.egRef.gen(right);
// Begin method
cg.emit.emitLabel(Label.method(arg.classSym(), ast.sym));
Pair<Register> regs = cgRef.egRef.genPushing(rhsReg, ast.left());
rhsReg = regs.a;
Register arrReg = regs.b;
if (!isNotNull) {
int padding = cgRef.emitCallPrefix(null, 1);
cgRef.push(arrReg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL);
cgRef.emitCallSuffix(null, 1, padding);
}
// 1. Save and update the base register, reserve space for local variables
cg.emit.emit("enter", ast.sym.locals.size() * VAR_SIZE, 0);
for (int i = 0; i < ast.sym.locals.size(); i++)
cg.emit.emitStore(0, -(i + 1) * VAR_SIZE, BASE_REG);
regs = cgRef.egRef.genPushing(arrReg, ast.right());
arrReg = regs.a;
Register idxReg = regs.b;
// 2. Save CPU registers
Register[] regs = RegisterManager.CALLEE_SAVE;
for (int i = 0; i < regs.length; i++)
cg.emit.emit("push", regs[i]);
// Check array bounds
if (emitBoundCheck) {
int padding = cgRef.emitCallPrefix(null, 2);
cgRef.push(idxReg.repr);
cgRef.push(arrReg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_BOUNDS);
cgRef.emitCallSuffix(null, 2, padding);
}
// 3. Run the code contained in the function
Register returnReg = visit(ast.body(), arg);
cg.emit.emitLabel(Label.returnMethod(arg.classSym(), ast.sym));
cgRef.emit.emitMove(rhsReg, arrayAddress(arrReg, idxReg));
cgRef.rm.releaseRegister(arrReg);
cgRef.rm.releaseRegister(idxReg);
cgRef.rm.releaseRegister(rhsReg);
// 4. Restore saved registers (if there is a mismatch, the stack will be corrupted)
for (int i = regs.length - 1; i >= 0; i--)
cg.emit.emit("pop", regs[i]);
return null;
}
// 5 and 6: Restore the old base pointer and return from the function
cg.emitMethodSuffix(returnReg == null);
@Override
protected Void dfltExpr(Expr ast, Expr arg) {
throw new RuntimeException("Store to unexpected lvalue " + ast);
}
// Framework bookkeeping
arg.leaveMethod();
return null;
}
@Override
public Register ifElse(IfElse ast, Location arg) {
// Emit condition check
Register conditionRegister = cg.eg.visit(ast.condition(), arg);
// If both blocks are empty, no more code need be generated, and the condition can be ignored
if (ast.then().children().isEmpty() && ast.otherwise().children().isEmpty()) {
// Generate the condition and ignore the result
cg.rm.releaseRegister(conditionRegister);
return null;
}
String notIfLabel = cg.emit.uniqueLabel();
String endLabel = cg.emit.uniqueLabel();
cg.emit.emit("cmp", TRUE, conditionRegister);
cg.rm.releaseRegister(conditionRegister);
if (!ast.then().children().isEmpty()) {
cg.emit.emit("jne", notIfLabel);
visit(ast.then(), arg);
// If there is no otherwise, the jump instruction makes no sense
// as the next code executed will be the next instruction
if (!ast.otherwise().children().isEmpty())
cg.emit.emit("jmp", endLabel);
} else {
// No if, therefore the else follows the condition immediately
cg.emit.emit("je", endLabel);
}
cg.emit.emitLabel(notIfLabel);
// Emit otherwise
visit(ast.otherwise(), arg);
cg.emit.emitLabel(endLabel);
// Check if the ifElse ast node contains a return statement
ReturnCheckerVisitor rc = new ReturnCheckerVisitor();
if (rc.ifElse(ast, null)) {
return Register.EAX;
} else {
return null;
}
}
@Override
public Register whileLoop(WhileLoop ast, Location arg) {
String conditionLabel = cg.emit.uniqueLabel();
cg.emit.emitLabel(conditionLabel);
Register conditionRegister = cg.eg.visit(ast.condition(), arg);
cg.emit.emit("cmp", TRUE, conditionRegister);
cg.rm.releaseRegister(conditionRegister);
if (ast.body().children().isEmpty()) {
// Jump structure can be easier if there is no body
cg.emit.emit("je", conditionLabel);
} else {
String endLabel = cg.emit.uniqueLabel();
// Emit jumps, labels and body
cg.emit.emit("jne", endLabel);
visit(ast.body(), arg);
cg.emit.emit("jmp", conditionLabel);
cg.emit.emitLabel(endLabel);
}
return null;
}
@Override
public Register assign(Assign ast, Location arg) {
Register value = cg.eg.visit(ast.right(), arg);
// If the type is a reference, visiting the lhs will yield its address
// else, the lhs will yield the current value. We need a visitor that
// returns a pointer to the position of the variable/field in memory
// for primitive types.
Register pointer = cg.eg.visit(ast.left(), arg.obtainReference());
if (ast.left().type.isReferenceType()) {
// Check null pointer
String validPointerLabel = cg.emit.uniqueLabel();
cg.emit.emit("cmp", 0, pointer);
cg.emit.emit("jne", validPointerLabel);
Interrupts.exit(cg, ExitCode.NULL_POINTER);
cg.emit.emitLabel(validPointerLabel);
}
cg.emit.emitStore(value, 0, pointer);
cg.rm.releaseRegister(pointer);
cg.rm.releaseRegister(value);
return null;
}
@Override
public Register builtInWrite(BuiltInWrite ast, Location arg) {
Register reg = cg.eg.visit(ast.arg(), arg);
cg.emit.emit("sub", 16, STACK_REG);
cg.emit.emitStore(reg, 4, STACK_REG);
cg.emit.emitStore("$STR_D", 0, STACK_REG);
cg.emit.emit("call", Config.PRINTF);
cg.emit.emit("add", 16, STACK_REG);
cg.rm.releaseRegister(reg);
return null;
}
@Override
public Register builtInWriteln(BuiltInWriteln ast, Location arg) {
cg.emit.emit("sub", 16, STACK_REG);
cg.emit.emitStore("$STR_NL", 0, STACK_REG);
cg.emit.emit("call", Config.PRINTF);
cg.emit.emit("add", 16, STACK_REG);
return null;
}
@Override
public Register returnStmt(ReturnStmt ast, Location arg) {
if (ast.arg() != null) {
Register retReg = cg.eg.visit(ast.arg(), arg);
cg.emit.emitMove(retReg, Register.EAX);
cg.rm.releaseRegister(retReg);
}
cg.emit.emit("jmp", Label.returnMethod(arg.classSym(), arg.methodSym()));
return Register.EAX;
}
@Override
public Register seq(Seq ast, Location arg) {
for (Ast instruction : ast.children()) {
Register res = visit(instruction, arg);
if (res != null)
return res; // don't generate instructions after a return
}
new AssignVisitor().visit(ast.left(), ast.right());
return null;
}
}

View File

@ -0,0 +1,40 @@
package cd.frontend.semantic;
import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.Var;
import cd.ir.AstRewriteVisitor;
import cd.ir.Symbol.ClassSymbol;
/**
* Runs after the semantic check and rewrites expressions to be more normalized.
* References to a field {@code foo} are rewritten to always use
* {@link Ast.Field} objects (i.e., {@code this.foo}.
*/
public class FieldQualifier {
public void rewrite(ClassDecl cd) {
AstRewriteVisitor<ClassSymbol> rewriter = new AstRewriteVisitor<ClassSymbol>() {
@Override
public Ast var(Var ast, ClassSymbol cs) {
switch (ast.sym.kind) {
case PARAM:
case LOCAL:
// Leave params or local variables alone
return ast;
case FIELD:
// Convert an implicit field reference to "this.foo"
Ast.Field f = new Ast.Field(new Ast.ThisRef(), ast.name);
f.arg().type = cs;
f.sym = ast.sym;
f.type = ast.type;
return f;
}
throw new RuntimeException("Unknown kind of var");
}
};
cd.accept(rewriter, cd.sym);
}
}

View File

@ -46,12 +46,13 @@ public class InheritanceChecker extends AstVisitor<Void, Void> {
// on number/type of parameters
MethodSymbol sym = ast.sym;
MethodSymbol superSym = classSym.superClass.getMethod(ast.name);
sym.overrides = superSym;
if (superSym != null) {
if (superSym.parameters.size() != sym.parameters.size())
throw new SemanticFailure(
Cause.INVALID_OVERRIDE,
"Overridden method %s has %d parameters, " +
"but original has %d",
"but original has %d",
ast.name, sym.parameters.size(),
superSym.parameters.size());
for (Pair<VariableSymbol> pair : Pair.zip(sym.parameters, superSym.parameters))
@ -59,13 +60,13 @@ public class InheritanceChecker extends AstVisitor<Void, Void> {
throw new SemanticFailure(
Cause.INVALID_OVERRIDE,
"Method parameter %s has type %s, but " +
"corresponding base class parameter %s has type %s",
"corresponding base class parameter %s has type %s",
pair.a.name, pair.a.type, pair.b.name, pair.b.type);
if (superSym.returnType != sym.returnType)
throw new SemanticFailure(
Cause.INVALID_OVERRIDE,
"Overridden method %s has return type %s," +
"but its superclass has %s",
"but its superclass has %s",
ast.name, sym.returnType, superSym.returnType);
}

View File

@ -5,11 +5,10 @@ import cd.ir.Ast.*;
import cd.ir.AstVisitor;
/**
* Visitor that checks if all paths of a given sequence have a
* return statement.
*
* <p>
* This visitor only needs to be used if are not using the Control Flow Graph.
*
* @author Leo Buttiker

View File

@ -19,12 +19,16 @@ public class SemanticAnalyzer {
}
public void check(List<ClassDecl> classDecls)
throws SemanticFailure {
throws SemanticFailure {
{
SymTable<TypeSymbol> typeSymbols = createSymbols(classDecls);
checkInheritance(classDecls);
checkStartPoint(typeSymbols);
checkMethodBodies(typeSymbols, classDecls);
{
rewriteMethodBodies(classDecls);
main.allTypeSymbols = typeSymbols.allSymbols();
}
}
}
@ -33,6 +37,7 @@ public class SemanticAnalyzer {
* as well as all classes and their fields and methods. Also
* creates a corresponding array symbol for every type
* (named {@code type[]}).
*
* @see SymbolCreator
*/
private SymTable<TypeSymbol> createSymbols(List<ClassDecl> classDecls) {
@ -57,7 +62,7 @@ public class SemanticAnalyzer {
// Create symbols for arrays of each type.
for (Symbol sym : new ArrayList<Symbol>(typeSymbols.localSymbols())) {
Symbol.ArrayTypeSymbol array =
new Symbol.ArrayTypeSymbol((TypeSymbol) sym);
new Symbol.ArrayTypeSymbol((TypeSymbol) sym);
typeSymbols.add(array);
}
@ -75,6 +80,7 @@ public class SemanticAnalyzer {
* classes, methods with different types, etc.
* Note that this must be run early because other code assumes
* that the inheritance is correct, for type checking etc.
*
* @see InheritanceChecker
*/
private void checkInheritance(List<ClassDecl> classDecls) {
@ -93,6 +99,7 @@ public class SemanticAnalyzer {
MethodSymbol mainMethod = cs.getMethod("main");
if (mainMethod != null && mainMethod.parameters.size() == 0 &&
mainMethod.returnType == PrimitiveTypeSymbol.voidType) {
main.mainType = cs;
return; // found the main() method!
}
}
@ -102,12 +109,12 @@ public class SemanticAnalyzer {
/**
* Check the bodies of methods for errors, particularly type errors
* but also undefined identifiers and the like.
*
* @see TypeChecker
*/
private void checkMethodBodies(
SymTable<TypeSymbol> typeSymbols,
List<ClassDecl> classDecls)
{
List<ClassDecl> classDecls) {
TypeChecker tc = new TypeChecker(typeSymbols);
for (ClassDecl classd : classDecls) {
@ -152,4 +159,9 @@ public class SemanticAnalyzer {
}
}
private void rewriteMethodBodies(List<ClassDecl> classDecls) {
for (ClassDecl cd : classDecls)
new FieldQualifier().rewrite(cd);
}
}

View File

@ -2,7 +2,8 @@ package cd.frontend.semantic;
/**
* Thrown by the semantic checker when a semantic error is detected
* in the user's program. */
* in the user's program.
*/
public class SemanticFailure extends RuntimeException {
private static final long serialVersionUID = 5375946759285719123L;
@ -12,26 +13,35 @@ public class SemanticFailure extends RuntimeException {
* Caused by an assignment to either a final field, {@code this},
* or some other kind of expression which cannot be assigned to.
* <b>Not</b> used for type errors in assignments, which fall
* under {@link #TYPE_ERROR}. */
* under {@link #TYPE_ERROR}.
*/
NOT_ASSIGNABLE,
/** Two variables, fields, methods, or classes with the same name
* were declared in the same scope */
/**
* Two variables, fields, methods, or classes with the same name
* were declared in the same scope
*/
DOUBLE_DECLARATION,
/** A field was accessed that does not exist */
/**
* A field was accessed that does not exist
*/
NO_SUCH_FIELD,
/** A method was called that does not exist */
/**
* A method was called that does not exist
*/
NO_SUCH_METHOD,
/**
* A variable or other identifier was used in a method
* body which has no corresponding declaration */
* body which has no corresponding declaration
*/
NO_SUCH_VARIABLE,
/**
* A method with a return type is missing a return statement among one of its paths */
* A method with a return type is missing a return statement among one of its paths
*/
MISSING_RETURN,
/**
@ -94,7 +104,9 @@ public class SemanticFailure extends RuntimeException {
*/
INVALID_OVERRIDE,
/** A method was called with the wrong number of arguments */
/**
* A method was called with the wrong number of arguments
*/
WRONG_NUMBER_OF_ARGUMENTS,
/**

View File

@ -3,14 +3,13 @@ package cd.frontend.semantic;
import cd.frontend.semantic.SemanticFailure.Cause;
import cd.ir.Symbol;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
/**
* A simple symbol table, with a pointer to the enclosing scope.
* Used by {@link TypeChecker} to store the various scopes for
* local, parameter, and field lookup. */
* local, parameter, and field lookup.
*/
public class SymTable<S extends Symbol> {
private final Map<String, S> map = new HashMap<String, S>();
@ -28,6 +27,17 @@ public class SymTable<S extends Symbol> {
map.put(sym.name, sym);
}
public List<S> allSymbols() {
List<S> result = new ArrayList<S>();
SymTable<S> st = this;
while (st != null) {
for (S sym : st.map.values())
result.add(sym);
st = st.parent;
}
return result;
}
public Collection<S> localSymbols() {
return this.map.values();
}
@ -51,7 +61,8 @@ public class SymTable<S extends Symbol> {
/**
* Base method: returns {@code null} if no symbol by that
* name can be found, in this table or in its parents */
* name can be found, in this table or in its parents
*/
public S get(String name) {
S res = map.get(name);
if (res != null)
@ -64,7 +75,8 @@ public class SymTable<S extends Symbol> {
/**
* Finds the symbol with the given name, or fails with a
* NO_SUCH_TYPE error. Only really makes sense to use this
* if S == TypeSymbol, but we don't strictly forbid it... */
* if S == TypeSymbol, but we don't strictly forbid it...
*/
public S getType(String name) {
S res = get(name);
if (res == null)

View File

@ -42,7 +42,7 @@ public class SymbolCreator extends Object {
* that there is not already an entry with the same name.
* If a symbol with the same name exists, throws an exception.
*/
public <S extends Symbol> void add(Map<String,S> map, S sym) {
public <S extends Symbol> void add(Map<String, S> map, S sym) {
if (map.containsKey(sym.name))
throw new SemanticFailure(
Cause.DOUBLE_DECLARATION,
@ -78,6 +78,8 @@ public class SymbolCreator extends Object {
ast.sym = new MethodSymbol(ast);
ast.sym.owner = classSym;
add(classSym.methods, ast.sym);
// create return type symbol

View File

@ -24,6 +24,7 @@ public class TypeChecker {
/**
* Checks whether two expressions have the same type
* and throws an exception otherwise.
*
* @return The common type of the two expression.
*/
private TypeSymbol checkTypesEqual(Expr leftExpr, Expr rightExpr, SymTable<VariableSymbol> locals) {
@ -32,10 +33,10 @@ public class TypeChecker {
if (leftType != rightType) {
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Expected operand types to be equal but found %s, %s",
leftType,
rightType);
Cause.TYPE_ERROR,
"Expected operand types to be equal but found %s, %s",
leftType,
rightType);
}
return leftType;
@ -121,7 +122,7 @@ public class TypeChecker {
@Override
public Void ifElse(IfElse ast, Void arg) {
checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
checkType((Expr) ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
visit(ast.then(), arg);
if (ast.otherwise() != null)
visit(ast.otherwise(), arg);
@ -138,7 +139,7 @@ public class TypeChecker {
@Override
public Void whileLoop(WhileLoop ast, Void arg) {
checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
checkType((Expr) ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
return visit(ast.body(), arg);
}
@ -150,7 +151,7 @@ public class TypeChecker {
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Return statement of method with void return type should "
+ "not have arguments.");
+ "not have arguments.");
} else if (!hasArg) {
// X m() { return; }
if (method.sym.returnType != PrimitiveTypeSymbol.voidType) {
@ -181,41 +182,41 @@ public class TypeChecker {
@Override
public TypeSymbol binaryOp(BinaryOp ast, SymTable<VariableSymbol> locals) {
switch (ast.operator) {
case B_TIMES:
case B_DIV:
case B_MOD:
case B_PLUS:
case B_MINUS:
TypeSymbol type = checkTypesEqual(ast.left(), ast.right(), locals);
checkTypeIsInt(type);
return type;
case B_TIMES:
case B_DIV:
case B_MOD:
case B_PLUS:
case B_MINUS:
TypeSymbol type = checkTypesEqual(ast.left(), ast.right(), locals);
checkTypeIsInt(type);
return type;
case B_AND:
case B_OR:
checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals);
checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
case B_AND:
case B_OR:
checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals);
checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
case B_EQUAL:
case B_NOT_EQUAL:
TypeSymbol left = typeExpr(ast.left(), locals);
TypeSymbol right = typeExpr(ast.right(), locals);
if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left))
return PrimitiveTypeSymbol.booleanType;
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Types %s and %s could never be equal",
left, right);
case B_EQUAL:
case B_NOT_EQUAL:
TypeSymbol left = typeExpr(ast.left(), locals);
TypeSymbol right = typeExpr(ast.right(), locals);
if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left))
return PrimitiveTypeSymbol.booleanType;
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Types %s and %s could never be equal",
left, right);
case B_LESS_THAN:
case B_LESS_OR_EQUAL:
case B_GREATER_THAN:
case B_GREATER_OR_EQUAL:
checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals));
return PrimitiveTypeSymbol.booleanType;
case B_LESS_THAN:
case B_LESS_OR_EQUAL:
case B_GREATER_THAN:
case B_GREATER_OR_EQUAL:
checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals));
return PrimitiveTypeSymbol.booleanType;
}
throw new RuntimeException("Unhandled operator "+ast.operator);
throw new RuntimeException("Unhandled operator " + ast.operator);
}
@Override
@ -297,19 +298,18 @@ public class TypeChecker {
public TypeSymbol unaryOp(UnaryOp ast, SymTable<VariableSymbol> locals) {
switch (ast.operator) {
case U_PLUS:
case U_MINUS:
{
TypeSymbol type = typeExpr(ast.arg(), locals);
checkTypeIsInt(type);
return type;
}
case U_PLUS:
case U_MINUS: {
TypeSymbol type = typeExpr(ast.arg(), locals);
checkTypeIsInt(type);
return type;
}
case U_BOOL_NOT:
checkType(ast.arg(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
case U_BOOL_NOT:
checkType(ast.arg(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
}
throw new RuntimeException("Unknown unary op "+ast.operator);
throw new RuntimeException("Unknown unary op " + ast.operator);
}
@Override
@ -348,7 +348,7 @@ public class TypeChecker {
// Check that the arguments are of correct type.
int i = 0;
for (Ast argAst : ast.argumentsWithoutReceiver())
checkType((Expr)argAst, mthd.parameters.get(i++).type, locals);
checkType((Expr) argAst, mthd.parameters.get(i++).type, locals);
return ast.sym.returnType;
@ -360,7 +360,8 @@ public class TypeChecker {
* Checks an expr as the left-hand-side of an assignment,
* returning the type of value that may be assigned there.
* May fail if the expression is not a valid LHS (for example,
* a "final" field). */
* a "final" field).
*/
private TypeSymbol typeLhs(Expr expr, SymTable<VariableSymbol> locals) {
return new LValueVisitor().visit(expr, locals);
}
@ -369,7 +370,9 @@ public class TypeChecker {
* @see TypeChecker#typeLhs(Expr, SymTable)
*/
private class LValueVisitor extends ExprVisitor<TypeSymbol, SymTable<VariableSymbol>> {
/** Fields, array-indexing, and vars can be on the LHS: */
/**
* Fields, array-indexing, and vars can be on the LHS:
*/
@Override
public TypeSymbol field(Field ast, SymTable<VariableSymbol> locals) {
@ -386,7 +389,9 @@ public class TypeChecker {
return typeExpr(ast, locals);
}
/** Any other kind of expression is not a value lvalue */
/**
* Any other kind of expression is not a value lvalue
*/
@Override
protected TypeSymbol dfltExpr(Expr ast, SymTable<VariableSymbol> locals) {
throw new SemanticFailure(

View File

@ -55,12 +55,22 @@ public abstract class Ast {
}
/** Accept method for the pattern Visitor. */
public abstract <R,A> R accept(AstVisitor<R, A> visitor, A arg);
/**
* Accept method for the pattern Visitor.
*/
public abstract <R, A> R accept(AstVisitor<R, A> visitor, A arg);
/** Convenient debugging printout */
/**
* Makes a deep clone of this AST node.
*/
public abstract Ast deepCopy();
/**
* Convenient debugging printout
*/
@Override
public String toString() {
public String toString() {
return String.format(
"(%s)@%x",
AstOneLine.toString(this),
@ -70,23 +80,30 @@ public abstract class Ast {
// _________________________________________________________________
// Expressions
/** Base class for all expressions */
/**
* Base class for all expressions
*/
public static abstract class Expr extends Ast {
protected Expr(int fixedCount) {
super(fixedCount);
}
/** Type that this expression will evaluate to (computed in semantic phase). */
/**
* Type that this expression will evaluate to (computed in semantic phase).
*/
public TypeSymbol type;
@Override
public <R,A> R accept(AstVisitor<R, A> visitor, A arg) {
return this.accept((ExprVisitor<R,A>)visitor, arg);
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return this.accept((ExprVisitor<R, A>) visitor, arg);
}
public abstract <R,A> R accept(ExprVisitor<R, A> visitor, A arg);
/** Copies any non-AST fields. */
public abstract <R, A> R accept(ExprVisitor<R, A> visitor, A arg);
/**
* Copies any non-AST fields.
*/
protected <E extends Expr> E postCopy(E item) {
{
item.type = type;
@ -95,9 +112,11 @@ public abstract class Ast {
}
}
/** Base class used for exprs with left/right operands.
* We use this for all expressions that take strictly two operands,
* such as binary operators or array indexing. */
/**
* Base class used for exprs with left/right operands.
* We use this for all expressions that take strictly two operands,
* such as binary operators or array indexing.
*/
public static abstract class LeftRightExpr extends Expr {
public LeftRightExpr(Expr left, Expr right) {
@ -108,14 +127,26 @@ public abstract class Ast {
setRight(right);
}
public Expr left() { return (Expr) this.rwChildren.get(0); }
public void setLeft(Expr node) { this.rwChildren.set(0, node); }
public Expr left() {
return (Expr) this.rwChildren.get(0);
}
public Expr right() { return (Expr) this.rwChildren.get(1); }
public void setRight(Expr node) { this.rwChildren.set(1, node); }
public void setLeft(Expr node) {
this.rwChildren.set(0, node);
}
public Expr right() {
return (Expr) this.rwChildren.get(1);
}
public void setRight(Expr node) {
this.rwChildren.set(1, node);
}
}
/** Base class used for expressions with a single argument */
/**
* Base class used for expressions with a single argument
*/
public static abstract class ArgExpr extends Expr {
public ArgExpr(Expr arg) {
@ -124,19 +155,28 @@ public abstract class Ast {
setArg(arg);
}
public Expr arg() { return (Expr) this.rwChildren.get(0); }
public void setArg(Expr node) { this.rwChildren.set(0, node); }
public Expr arg() {
return (Expr) this.rwChildren.get(0);
}
public void setArg(Expr node) {
this.rwChildren.set(0, node);
}
}
/** Base class used for things with no arguments */
protected static abstract class LeafExpr extends Expr {
/**
* Base class used for things with no arguments
*/
public static abstract class LeafExpr extends Expr {
public LeafExpr() {
super(0);
}
}
/** Represents {@code this}, the current object */
/**
* Represents {@code this}, the current object
*/
public static class ThisRef extends LeafExpr {
@Override
@ -144,30 +184,40 @@ public abstract class Ast {
return visitor.thisRef(this, arg);
}
@Override
public ThisRef deepCopy() {
return postCopy(new ThisRef());
}
}
/** A binary operation combining a left and right operand,
* such as "1+2" or "3*4" */
/**
* A binary operation combining a left and right operand,
* such as "1+2" or "3*4"
*/
public static class BinaryOp extends LeftRightExpr {
public static enum BOp {
public static enum BOp {
B_TIMES("*"),
B_DIV("/"),
B_MOD("%"),
B_PLUS("+"),
B_MINUS("-"),
B_AND("&&"),
B_OR("||"),
B_EQUAL("=="),
B_NOT_EQUAL("!="),
B_LESS_THAN("<"),
B_LESS_OR_EQUAL("<="),
B_GREATER_THAN(">"),
B_GREATER_OR_EQUAL(">=");
B_TIMES("*"),
B_DIV("/"),
B_MOD("%"),
B_PLUS("+"),
B_MINUS("-"),
B_AND("&&"),
B_OR("||"),
B_EQUAL("=="),
B_NOT_EQUAL("!="),
B_LESS_THAN("<"),
B_LESS_OR_EQUAL("<="),
B_GREATER_THAN(">"),
B_GREATER_OR_EQUAL(">=");
public String repr;
private BOp(String repr) { this.repr = repr; }
public String repr;
private BOp(String repr) {
this.repr = repr;
}
/**
* Note that this method ignores short-circuit evaluation of boolean
@ -177,35 +227,44 @@ public abstract class Ast {
* operator.
*/
public boolean isCommutative() {
switch(this) {
case B_PLUS:
case B_TIMES:
case B_AND:
case B_OR:
case B_EQUAL:
case B_NOT_EQUAL:
return true;
default:
return false;
}
switch (this) {
case B_PLUS:
case B_TIMES:
case B_AND:
case B_OR:
case B_EQUAL:
case B_NOT_EQUAL:
return true;
default:
return false;
}
}
};
}
public BOp operator;
;
public BinaryOp(Expr left, BOp operator, Expr right) {
super(left, right);
this.operator = operator;
}
public BOp operator;
public BinaryOp(Expr left, BOp operator, Expr right) {
super(left, right);
this.operator = operator;
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.binaryOp(this, arg);
}
@Override
public BinaryOp deepCopy() {
return postCopy(new BinaryOp(left(), operator, right()));
}
}
/** A Cast from one type to another: {@code (typeName)arg} */
/**
* A Cast from one type to another: {@code (typeName)arg}
*/
public static class Cast extends ArgExpr {
public String typeName;
@ -220,9 +279,14 @@ public abstract class Ast {
return visitor.cast(this, arg);
}
@Override
public Cast deepCopy() {
return postCopy(new Cast(arg(), typeName));
}
@Override
protected <E extends Expr> E postCopy(E item) {
((Cast)item).type = type;
((Cast) item).type = type;
return super.postCopy(item);
}
@ -231,6 +295,7 @@ public abstract class Ast {
public static class IntConst extends LeafExpr {
public final int value;
public IntConst(int value) {
this.value = value;
}
@ -240,11 +305,17 @@ public abstract class Ast {
return visitor.intConst(this, arg);
}
@Override
public IntConst deepCopy() {
return postCopy(new IntConst(value));
}
}
public static class BooleanConst extends LeafExpr {
public final boolean value;
public BooleanConst(boolean value) {
this.value = value;
}
@ -254,6 +325,11 @@ public abstract class Ast {
return visitor.booleanConst(this, arg);
}
@Override
public BooleanConst deepCopy() {
return postCopy(new BooleanConst(value));
}
}
public static class NullConst extends LeafExpr {
@ -263,6 +339,11 @@ public abstract class Ast {
return visitor.nullConst(this, arg);
}
@Override
public NullConst deepCopy() {
return postCopy(new NullConst());
}
}
public static class Field extends ArgExpr {
@ -282,9 +363,14 @@ public abstract class Ast {
return visitor.field(this, arg);
}
@Override
public Field deepCopy() {
return postCopy(new Field(arg(), fieldName));
}
@Override
protected <E extends Expr> E postCopy(E item) {
((Field)item).sym = sym;
((Field) item).sym = sym;
return super.postCopy(item);
}
@ -301,11 +387,18 @@ public abstract class Ast {
return visitor.index(this, arg);
}
@Override
public Index deepCopy() {
return postCopy(new Index(left(), right()));
}
}
public static class NewObject extends LeafExpr {
/** Name of the type to be created */
/**
* Name of the type to be created
*/
public String typeName;
public NewObject(String typeName) {
@ -317,11 +410,18 @@ public abstract class Ast {
return visitor.newObject(this, arg);
}
@Override
public NewObject deepCopy() {
return postCopy(new NewObject(typeName));
}
}
public static class NewArray extends ArgExpr {
/** Name of the type to be created: must be an array type */
/**
* Name of the type to be created: must be an array type
*/
public String typeName;
public NewArray(String typeName, Expr capacity) {
@ -334,21 +434,31 @@ public abstract class Ast {
return visitor.newArray(this, arg);
}
@Override
public NewArray deepCopy() {
return postCopy(new NewArray(typeName, arg()));
}
}
public static class UnaryOp extends ArgExpr {
public static enum UOp {
U_PLUS("+"),
U_MINUS("-"),
U_BOOL_NOT("!");
public String repr;
private UOp(String repr) { this.repr = repr; }
};
public static enum UOp {
U_PLUS("+"),
U_MINUS("-"),
U_BOOL_NOT("!");
public String repr;
public final UOp operator;
private UOp(String repr) {
this.repr = repr;
}
}
public UnaryOp(UOp operator, Expr arg) {
;
public final UOp operator;
public UnaryOp(UOp operator, Expr arg) {
super(arg);
this.operator = operator;
}
@ -358,6 +468,11 @@ public abstract class Ast {
return visitor.unaryOp(this, arg);
}
@Override
public UnaryOp deepCopy() {
return postCopy(new UnaryOp(operator, arg()));
}
}
public static class Var extends LeafExpr {
@ -373,6 +488,7 @@ public abstract class Ast {
public Var(String name) {
this.name = name;
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.var(this, arg);
@ -390,9 +506,14 @@ public abstract class Ast {
return v;
}
@Override
public Var deepCopy() {
return postCopy(new Var(name));
}
@Override
protected <E extends Expr> E postCopy(E item) {
((Var)item).sym = sym;
((Var) item).sym = sym;
return super.postCopy(item);
}
@ -410,6 +531,11 @@ public abstract class Ast {
return visitor.builtInRead(this, arg);
}
@Override
public BuiltInRead deepCopy() {
return postCopy(new BuiltInRead());
}
}
public static class MethodCallExpr extends Expr {
@ -426,28 +552,38 @@ public abstract class Ast {
this.rwChildren.addAll(arguments);
}
/** Returns the receiver of the method call.
* i.e., for a method call {@code a.b(c,d)} returns {@code a}. */
public Expr receiver() { return (Expr) this.rwChildren.get(0); }
/**
* Returns the receiver of the method call.
* i.e., for a method call {@code a.b(c,d)} returns {@code a}.
*/
public Expr receiver() {
return (Expr) this.rwChildren.get(0);
}
/** Changes the receiver of the method call.
* i.e., for a method call {@code a.b(c,d)} changes {@code a}. */
public void setReceiver(Expr rcvr) { this.rwChildren.set(0, rcvr); }
/**
* Changes the receiver of the method call.
* i.e., for a method call {@code a.b(c,d)} changes {@code a}.
*/
public void setReceiver(Expr rcvr) {
this.rwChildren.set(0, rcvr);
}
/** Returns all arguments to the method, <b>including the receiver.</b>
* i.e, for a method call {@code a.b(c,d)} returns {@code [a, c, d]} */
public List<Expr> allArguments()
{
/**
* Returns all arguments to the method, <b>including the receiver.</b>
* i.e, for a method call {@code a.b(c,d)} returns {@code [a, c, d]}
*/
public List<Expr> allArguments() {
ArrayList<Expr> result = new ArrayList<Expr>();
for (Ast chi : this.rwChildren)
result.add((Expr) chi);
return Collections.unmodifiableList(result);
}
/** Returns all arguments to the method, without the receiver.
* i.e, for a method call {@code a.b(c,d)} returns {@code [c, d]} */
public List<Expr> argumentsWithoutReceiver()
{
/**
* Returns all arguments to the method, without the receiver.
* i.e, for a method call {@code a.b(c,d)} returns {@code [c, d]}
*/
public List<Expr> argumentsWithoutReceiver() {
ArrayList<Expr> result = new ArrayList<Expr>();
for (int i = 1; i < this.rwChildren.size(); i++)
result.add((Expr) this.rwChildren.get(i));
@ -460,19 +596,40 @@ public abstract class Ast {
return visitor.methodCall(this, arg);
}
public List<Expr> deepCopyArguments() {
ArrayList<Expr> result = new ArrayList<Expr>();
for (final Expr expr : argumentsWithoutReceiver()) {
result.add((Expr) expr.deepCopy());
}
return result;
}
@Override
public MethodCallExpr deepCopy() {
return postCopy(new MethodCallExpr((Expr) receiver().deepCopy(), methodName, deepCopyArguments()));
}
}
// _________________________________________________________________
// Statements
/** Interface for all statements */
/**
* Interface for all statements
*/
public static abstract class Stmt extends Ast {
protected Stmt(int fixedCount) {
super(fixedCount);
}
}
/** Represents an empty statement: has no effect. */
/**
* Represents an empty statement: has no effect.
*/
public static class Nop extends Stmt {
public Nop() {
@ -484,10 +641,16 @@ public abstract class Ast {
return visitor.nop(this, arg);
}
@Override
public Ast deepCopy() {
return new Nop();
}
}
/** An assignment from {@code right()} to the location
* represented by {@code left()}.
/**
* An assignment from {@code right()} to the location
* represented by {@code left()}.
*/
public static class Assign extends Stmt {
@ -498,17 +661,32 @@ public abstract class Ast {
setRight(right);
}
public Expr left() { return (Expr) this.rwChildren.get(0); }
public void setLeft(Expr node) { this.rwChildren.set(0, node); }
public Expr left() {
return (Expr) this.rwChildren.get(0);
}
public Expr right() { return (Expr) this.rwChildren.get(1); }
public void setRight(Expr node) { this.rwChildren.set(1, node); }
public void setLeft(Expr node) {
this.rwChildren.set(0, node);
}
public Expr right() {
return (Expr) this.rwChildren.get(1);
}
public void setRight(Expr node) {
this.rwChildren.set(1, node);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.assign(this, arg);
}
@Override
public Ast deepCopy() {
return new Assign((Expr) left().deepCopy(), (Expr) right().deepCopy());
}
}
public static class IfElse extends Stmt {
@ -521,20 +699,40 @@ public abstract class Ast {
setOtherwise(otherwise);
}
public Expr condition() { return (Expr) this.rwChildren.get(0); }
public void setCondition(Expr node) { this.rwChildren.set(0, node); }
public Expr condition() {
return (Expr) this.rwChildren.get(0);
}
public Ast then() { return this.rwChildren.get(1); }
public void setThen(Ast node) { this.rwChildren.set(1, node); }
public void setCondition(Expr node) {
this.rwChildren.set(0, node);
}
public Ast otherwise() { return this.rwChildren.get(2); }
public void setOtherwise(Ast node) { this.rwChildren.set(2, node); }
public Ast then() {
return this.rwChildren.get(1);
}
public void setThen(Ast node) {
this.rwChildren.set(1, node);
}
public Ast otherwise() {
return this.rwChildren.get(2);
}
public void setOtherwise(Ast node) {
this.rwChildren.set(2, node);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.ifElse(this, arg);
}
@Override
public Ast deepCopy() {
return new IfElse((Expr) condition().deepCopy(), then().deepCopy(), otherwise().deepCopy());
}
}
@ -545,14 +743,24 @@ public abstract class Ast {
setArg(arg);
}
public Expr arg() { return (Expr) this.rwChildren.get(0); }
public void setArg(Expr node) { this.rwChildren.set(0, node); }
public Expr arg() {
return (Expr) this.rwChildren.get(0);
}
public void setArg(Expr node) {
this.rwChildren.set(0, node);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.returnStmt(this, arg);
}
@Override
public Ast deepCopy() {
return new ReturnStmt(arg() != null ? (Expr) arg().deepCopy() : null);
}
}
public static class BuiltInWrite extends Stmt {
@ -563,14 +771,24 @@ public abstract class Ast {
setArg(arg);
}
public Expr arg() { return (Expr) this.rwChildren.get(0); }
public void setArg(Expr node) { this.rwChildren.set(0, node); }
public Expr arg() {
return (Expr) this.rwChildren.get(0);
}
public void setArg(Expr node) {
this.rwChildren.set(0, node);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.builtInWrite(this, arg);
}
@Override
public Ast deepCopy() {
return new BuiltInWrite((Expr) arg().deepCopy());
}
}
public static class BuiltInWriteln extends Stmt {
@ -584,6 +802,11 @@ public abstract class Ast {
return visitor.builtInWriteln(this, arg);
}
@Override
public Ast deepCopy() {
return new BuiltInWriteln();
}
}
public static class MethodCall extends Stmt {
@ -594,7 +817,7 @@ public abstract class Ast {
}
public MethodCallExpr getMethodCallExpr() {
return (MethodCallExpr)this.rwChildren.get(0);
return (MethodCallExpr) this.rwChildren.get(0);
}
@Override
@ -602,6 +825,11 @@ public abstract class Ast {
return visitor.methodCall(this, arg);
}
@Override
public Ast deepCopy() {
return new MethodCall(this.getMethodCallExpr().deepCopy());
}
}
public static class WhileLoop extends Stmt {
@ -613,22 +841,39 @@ public abstract class Ast {
setBody(body);
}
public Expr condition() { return (Expr) this.rwChildren.get(0); }
public void setCondition(Expr cond) { this.rwChildren.set(0, cond); }
public Expr condition() {
return (Expr) this.rwChildren.get(0);
}
public Ast body() { return this.rwChildren.get(1); }
public void setBody(Ast body) { this.rwChildren.set(1, body); }
public void setCondition(Expr cond) {
this.rwChildren.set(0, cond);
}
public Ast body() {
return this.rwChildren.get(1);
}
public void setBody(Ast body) {
this.rwChildren.set(1, body);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.whileLoop(this, arg);
}
@Override
public Ast deepCopy() {
return new WhileLoop((Expr) condition().deepCopy(), body().deepCopy());
}
}
// _________________________________________________________________
// Declarations
/** Interface for all declarations */
/**
* Interface for all declarations
*/
public static abstract class Decl extends Ast {
protected Decl(int fixedCount) {
super(fixedCount);
@ -656,10 +901,17 @@ public abstract class Ast {
return visitor.varDecl(this, arg);
}
@Override
public Ast deepCopy() {
return new VarDecl(type, name);
}
}
/** Used in {@link MethodDecl} to group together declarations
* and method bodies. */
/**
* Used in {@link MethodDecl} to group together declarations
* and method bodies.
*/
public static class Seq extends Decl {
public Seq(List<Ast> nodes) {
@ -667,7 +919,9 @@ public abstract class Ast {
if (nodes != null) this.rwChildren.addAll(nodes);
}
/** Grant access to the raw list of children for seq nodes */
/**
* Grant access to the raw list of children for seq nodes
*/
public List<Ast> rwChildren() {
return this.rwChildren;
}
@ -676,6 +930,19 @@ public abstract class Ast {
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.seq(this, arg);
}
@Override
public Ast deepCopy() {
List<Ast> result = new ArrayList<Ast>();
for (final Ast ast : this.rwChildren) {
result.add(ast.deepCopy());
}
return new Seq(result);
}
}
public static class MethodDecl extends Decl {
@ -685,6 +952,8 @@ public abstract class Ast {
public List<String> argumentTypes;
public List<String> argumentNames;
public MethodSymbol sym;
public ControlFlowGraph cfg;
public MethodDecl(
String returnType,
String name,
@ -693,6 +962,7 @@ public abstract class Ast {
Seq body) {
this(returnType, name, Pair.unzipA(formalParams), Pair.unzipB(formalParams), decls, body);
}
public MethodDecl(
String returnType,
String name,
@ -709,17 +979,38 @@ public abstract class Ast {
setBody(body);
}
public Seq decls() { return (Seq) this.rwChildren.get(0); }
public void setDecls(Seq decls) { this.rwChildren.set(0, decls); }
public Seq decls() {
return (Seq) this.rwChildren.get(0);
}
public Seq body() { return (Seq) this.rwChildren.get(1); }
public void setBody(Seq body) { this.rwChildren.set(1, body); }
public void setDecls(Seq decls) {
this.rwChildren.set(0, decls);
}
public Seq body() {
return (Seq) this.rwChildren.get(1);
}
public void setBody(Seq body) {
this.rwChildren.set(1, body);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.methodDecl(this, arg);
}
@Override
public Ast deepCopy() {
return new MethodDecl(
returnType,
name,
Collections.unmodifiableList(argumentTypes),
Collections.unmodifiableList(argumentNames),
(Seq) decls().deepCopy(),
(Seq) body().deepCopy());
}
}
public static class ClassDecl extends Decl {
@ -755,5 +1046,17 @@ public abstract class Ast {
return visitor.classDecl(this, arg);
}
@Override
public Ast deepCopy() {
List<Ast> result = new ArrayList<Ast>();
for (final Ast ast : members()) {
result.add(ast.deepCopy());
}
return new ClassDecl(name, superClass, result);
}
}
}

View File

@ -0,0 +1,51 @@
package cd.ir;
import cd.ir.Ast.Expr;
import cd.ir.Symbol.PrimitiveTypeSymbol;
import java.util.Map;
/**
* A visitor that replaces AST nodes in toBeReplaced
* used in ConstantAnalysis, deletes all the variables that have a constant value and replace them with a constant
*/
public class AstReplaceVisitor extends AstRewriteVisitor<Ast> {
// use this information to replace each Var node with it's correct constant value
public Map<Ast.Expr, Ast.LeafExpr> toBeReplaced;
public Map<Expr, Integer> initializePositions;
public int currentPosition;
public Ast.LeafExpr getReplacement(Ast.Var arg) {
// Var was declared constant but initialization happens later
// replace it with IntConst or BooleanConst and their default value
if (toBeReplaced.containsKey(arg) && currentPosition <= initializePositions.get(arg)) {
//if (toBeReplaced.containsKey(arg)) {
Ast.LeafExpr leafEpr = (Ast.LeafExpr) arg;
if (leafEpr.type == PrimitiveTypeSymbol.intType) {
return new Ast.IntConst(0);
} else {
return new Ast.BooleanConst(false);
}
}
// Var was declared constant and initialization already happened
else if (toBeReplaced.containsKey(arg) && currentPosition > initializePositions.get(arg)) {
return toBeReplaced.get(arg);
}
// Var was not declared to be a constant
else {
return arg;
}
}
public Ast var(Ast.Var ast, Void arg) {
return getReplacement(ast);
}
}

View File

@ -26,6 +26,7 @@ public class AstRewriteVisitor<A> extends AstVisitor<Ast, A> {
* <p>
* The default implementation does nothing.
*/
protected void nodeReplaced(Ast oldNode, Ast newNode) {}
protected void nodeReplaced(Ast oldNode, Ast newNode) {
}
}

View File

@ -4,14 +4,17 @@ import cd.ir.Ast.Decl;
import cd.ir.Ast.Expr;
import cd.ir.Ast.Stmt;
/** A visitor that visits any kind of node */
public class AstVisitor<R,A> extends ExprVisitor<R,A> {
/**
* A visitor that visits any kind of node
*/
public class AstVisitor<R, A> extends ExprVisitor<R, A> {
/**
* Recurse and process {@code ast}. It is preferred to
* call this rather than calling accept directly, since
* it can be overloaded to introduce memoization,
* for example. */
* for example.
*/
public R visit(Ast ast, A arg) {
return ast.accept(this, arg);
}
@ -43,19 +46,22 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
/**
* The default action for default actions is to call this,
* which simply recurses to any children. Also called
* by seq() by default. */
* by seq() by default.
*/
protected R dflt(Ast ast, A arg) {
return visitChildren(ast, arg);
}
/**
* The default action for statements is to call this */
* The default action for statements is to call this
*/
protected R dfltStmt(Stmt ast, A arg) {
return dflt(ast, arg);
}
/**
* The default action for expressions is to call this */
* The default action for expressions is to call this
*/
@Override
protected R dfltExpr(Expr ast, A arg) {
return dflt(ast, arg);
@ -63,7 +69,8 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
/**
* The default action for AST nodes representing declarations
* is to call this function */
* is to call this function
*/
protected R dfltDecl(Decl ast, A arg) {
return dflt(ast, arg);
}

113
src/cd/ir/BasicBlock.java Normal file
View File

@ -0,0 +1,113 @@
package cd.ir;
import cd.ir.Ast.Expr;
import cd.ir.Ast.Stmt;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Node in a control flow graph. New instances should be created
* via the methods in {@link ControlFlowGraph}.
* Basic blocks consist of a list of statements ({@link #stmts}) which are
* executed at runtime. When the basic block ends, control flows into its
* {@link #successors}. If the block has more than one successor, it must also
* have a non-{@code null} value for {@link #condition}, which describes an expression
* that will determine which successor to take. Basic blocks also have fields
* for storing the parent and children in the dominator tree. These are generally computed
* in a second pass once the graph is fully built.
* <p>
* Your team will have to write code that builds the control flow graph and computes the
* relevant dominator information.
*/
public class BasicBlock {
/**
* Unique numerical index assigned by CFG builder between 0 and the total number of
* basic blocks. Useful for indexing into arrays and the like.
*/
public final int index;
/**
* List of predecessor blocks in the flow graph (i.e., blocks for
* which {@code this} is a successor).
*/
public final List<BasicBlock> predecessors = new ArrayList<BasicBlock>();
/**
* List of successor blocks in the flow graph (those that come after the
* current block). This list is always either of size 0, 1 or 2: 1 indicates
* that control flow continues directly into the next block, and 2 indicates
* that control flow goes in one of two directions, depending on the
* value that results when {@link #condition} is evaluated at runtime.
* If there are two successors, then the 0th entry is taken when {@code condition}
* evaluates to {@code true}.
*
* @see #trueSuccessor()
* @see #falseSuccessor()
*/
public final List<BasicBlock> successors = new ArrayList<BasicBlock>();
/**
* List of statements in this basic block.
*/
public final List<Stmt> stmts = new ArrayList<>();
/**
* If non-null, indicates that this basic block should have
* two successors. Control flows to the first successor if
* this condition evaluates at runtime to true, otherwise to
* the second successor. If null, the basic block should have
* only one successor.
*/
public Expr condition;
/**
* Parent of this basic block in the dominator tree (initially null until computed).
* Otherwise known as the immediate dominator.
*/
public BasicBlock dominatorTreeParent = null;
/**
* Children of this basic block in the dominator tree (initially empty until
* computed).
*/
public final List<BasicBlock> dominatorTreeChildren = new ArrayList<BasicBlock>();
/**
* Contains the dominance frontier of this block. A block b is in the dominance
* frontier of another block c if c does not dominate b, but c DOES dominate a
* predecessor of b.
*/
public final Set<BasicBlock> dominanceFrontier = new HashSet<BasicBlock>();
public BasicBlock(int index) {
this.index = index;
}
public BasicBlock trueSuccessor() {
assert this.condition != null;
return this.successors.get(0);
}
public BasicBlock falseSuccessor() {
assert this.condition != null;
return this.successors.get(1);
}
public <A, B> A accept(AstVisitor<A, B> visitor, B arg) {
A lastA = null;
for (Stmt stmt : stmts)
lastA = visitor.visit(stmt, arg);
if (condition != null)
lastA = visitor.visit(condition, arg);
return lastA;
}
@Override
public String toString() {
return "BB" + index;
}
}

View File

@ -0,0 +1,135 @@
package cd.ir;
import cd.ir.Ast.BinaryOp;
import cd.ir.Ast.BooleanConst;
import cd.ir.Ast.Expr;
import cd.ir.Ast.UnaryOp;
import cd.ir.Symbol.VariableSymbol;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static cd.Config.FALSE;
import static cd.Config.TRUE;
/**
* A visitor that only visits {@link Expr} nodes.
* calculates the compile time value of an rhs in an assignment statement or the boolean value of a condition
*/
public class CompileTimeEvaluator extends ExprVisitor<Integer, Map<VariableSymbol, Integer>> {
// if the expression contains non constant variables, field or index; or if the expression is a read()
// no compile Time value can be computed
private boolean failedToEvaluate;
public Optional<Integer> calc(Ast.Expr ast) {
return calc(ast, new HashMap<>());
}
public Optional<Integer> calc(Ast.Expr ast, Map<VariableSymbol, Integer> constantMap) {
try {
failedToEvaluate = false;
Integer result = visit(ast, constantMap);
if (failedToEvaluate)
return Optional.empty();
else
return Optional.of(result);
} catch (ArithmeticException e) {
return Optional.empty();
}
}
@Override
protected Integer dfltExpr(Expr ast, Map<VariableSymbol, Integer> arg) {
failedToEvaluate = true;
return Integer.MIN_VALUE;
}
@Override
public Integer binaryOp(BinaryOp ast, Map<VariableSymbol, Integer> arg) {
boolean tempResult;
int left = visit(ast.left(), arg);
int right = visit(ast.right(), arg);
switch (ast.operator) {
case B_TIMES:
return left * right;
case B_PLUS:
return left + right;
case B_MINUS:
return left - right;
case B_DIV:
return left / right;
case B_MOD:
return left % right;
case B_AND:
tempResult = (left == TRUE) && (right == TRUE);
break;
case B_OR:
tempResult = (left == TRUE) || (right == TRUE);
break;
case B_EQUAL:
tempResult = left == right;
break;
case B_NOT_EQUAL:
tempResult = left != right;
break;
case B_LESS_THAN:
tempResult = left < right;
break;
case B_LESS_OR_EQUAL:
tempResult = left <= right;
break;
case B_GREATER_THAN:
tempResult = left > right;
break;
case B_GREATER_OR_EQUAL:
tempResult = left >= right;
break;
default:
throw new RuntimeException("Invalid binary operator");
}
return tempResult ? TRUE : FALSE;
}
@Override
public Integer unaryOp(UnaryOp ast, Map<VariableSymbol, Integer> arg) {
int result = visit(ast.arg(), arg);
switch (ast.operator) {
case U_PLUS:
return result;
case U_MINUS:
return -result;
case U_BOOL_NOT:
return result == FALSE ? TRUE : FALSE;
default:
throw new RuntimeException("Invalid unary operator");
}
}
@Override
public Integer booleanConst(BooleanConst ast, Map<VariableSymbol, Integer> arg) {
return ast.value ? TRUE : FALSE;
}
@Override
public Integer intConst(Ast.IntConst ast, Map<VariableSymbol, Integer> arg) {
return ast.value;
}
// check if a given Variable has a constant value
@Override
public Integer var(Ast.Var ast, Map<VariableSymbol, Integer> arg) {
if (arg.containsKey(ast.sym)) {
return arg.get(ast.sym);
} else {
failedToEvaluate = true;
return Integer.MIN_VALUE;
}
}
}

View File

@ -0,0 +1,61 @@
package cd.ir;
import cd.ir.Ast.Expr;
import java.util.ArrayList;
import java.util.List;
/**
* Represents the control flow graph of a single method.
*/
public class ControlFlowGraph {
public BasicBlock start, end;
public final List<BasicBlock> allBlocks = new ArrayList<BasicBlock>();
public int count() {
return allBlocks.size();
}
public BasicBlock newBlock() {
BasicBlock blk = new BasicBlock(count());
allBlocks.add(blk);
return blk;
}
/**
* Given a list of basic blocks that do not yet have successors,
* merges their control flows into a single successor and returns
* the new successor.
*/
public BasicBlock join(BasicBlock... pred) {
BasicBlock result = newBlock();
for (BasicBlock p : pred) {
assert p.condition == null;
assert p.successors.size() == 0;
p.successors.add(result);
result.predecessors.add(p);
}
return result;
}
/**
* Terminates {@code blk} so that it evaluates {@code cond},
* and creates two new basic blocks, one for the case where
* the result is true, and one for the case where the result is
* false.
*/
public void terminateInCondition(BasicBlock blk, Expr cond) {
assert blk.condition == null;
assert blk.successors.size() == 0;
blk.condition = cond;
blk.successors.add(newBlock());
blk.successors.add(newBlock());
blk.trueSuccessor().predecessors.add(blk);
blk.falseSuccessor().predecessors.add(blk);
}
public void connect(BasicBlock from, BasicBlock to) {
to.predecessors.add(from);
from.successors.add(to);
}
}

View File

@ -5,12 +5,13 @@ import cd.ir.Ast.Expr;
/**
* A visitor that only visits {@link Expr} nodes.
*/
public class ExprVisitor<R,A> {
public class ExprVisitor<R, A> {
/**
* Recurse and process {@code ast}. It is preferred to
* call this rather than calling accept directly, since
* it can be overloaded to introduce memoization,
* for example. */
* for example.
*/
public R visit(Expr ast, A arg) {
return ast.accept(this, arg);
}
@ -22,14 +23,15 @@ public class ExprVisitor<R,A> {
public R visitChildren(Expr ast, A arg) {
R lastValue = null;
for (Ast child : ast.children())
lastValue = visit((Expr)child, arg);
lastValue = visit((Expr) child, arg);
return lastValue;
}
/**
* The default action for default actions is to call this,
* which simply recurses to any children. Also called
* by seq() by default. */
* by seq() by default.
*/
protected R dfltExpr(Expr ast, A arg) {
return visitChildren(ast, arg);
}

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