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" />
<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" />
<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="${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

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

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;
@ -20,26 +24,29 @@ import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* The main entrypoint for the compiler. Consists of a series
* of routines which must be invoked in order. The main()
* routine here invokes these routines, as does the unit testing
* code. This is not the <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
public Writer debug = null;
// Set to non-null to write dump of control flow graph
public File cfgdumpbase;
/** Symbol for the Main type */
public Symbol.ClassSymbol mainType;
/** List of all type symbols, used by code generator. */
public List<TypeSymbol> allTypeSymbols;
/**
* Symbol for the Main type
*/
public ClassSymbol mainType;
/**
* List of all type symbols, used by code generator.
*/
public List<TypeSymbol> allTypeSymbols;
public void debug(String format, Object... args) {
if (debug != null) {
@ -53,23 +60,28 @@ public class Main {
}
}
}
/** Parse command line, invoke compile() routine */
/**
* Parse command line, invoke compile() routine
*/
public static void main(String args[]) throws IOException {
Main m = new Main();
for (String arg : args) {
if (arg.equals("-d"))
m.debug = new OutputStreamWriter(System.err);
else {
if (m.debug != null)
m.cfgdumpbase = new File(arg);
FileReader fin = new FileReader(arg);
// Parse:
List<ClassDecl> astRoots = m.parse(fin);
// Run the semantic check:
m.semanticCheck(astRoots);
// Generate code:
String sFile = arg + Config.ASMEXT;
try (FileWriter fout = new FileWriter(sFile)) {
@ -78,51 +90,63 @@ public class Main {
}
}
}
/** Parses an input stream into an AST
* @throws IOException */
/**
* Parses an input stream into an AST
*
* @throws IOException
*/
public List<ClassDecl> parse(Reader reader) throws IOException {
List<ClassDecl> result = new ArrayList<ClassDecl>();
try {
JavaliLexer lexer = new JavaliLexer(new ANTLRInputStream(reader));
JavaliParser parser = new JavaliParser(new CommonTokenStream(lexer));
parser.setErrorHandler(new BailErrorStrategy());
UnitContext unit = parser.unit();
JavaliAstVisitor visitor = new JavaliAstVisitor();
visitor.visit(unit);
result = visitor.classDecls;
result = visitor.classDecls;
} catch (ParseCancellationException e) {
ParseFailure pf = new ParseFailure(0, "?");
pf.initCause(e);
throw pf;
}
debug("AST Resulting From Parsing Stage:");
dumpAst(result);
return result;
}
// TODO: decide how to do/call optimization
public void semanticCheck(List<ClassDecl> astRoots) {
{
new SemanticAnalyzer(this).check(astRoots);
}
}
public void generateCode(List<ClassDecl> astRoots, Writer out) {
{
AstCodeGenerator cg = AstCodeGenerator.createCodeGenerator(this, out);
cg.go(astRoots);
}
new SemanticAnalyzer(this).check(astRoots);
// Build control flow graph:
for (ClassDecl cd : astRoots)
for (MethodDecl md : cd.methods()) {
new CfgBuilder().build(md);
}
CfgDump.toString(astRoots, ".cfg", cfgdumpbase, false);
// Optimize
new AstOptimizer().go(astRoots, allTypeSymbols);
CfgDump.toString(astRoots, ".cfgOPT", cfgdumpbase, false);
}
/** Dumps the AST to the debug stream */
public void generateCode(List<ClassDecl> astRoots, Writer out) {
CfgCodeGenerator cg = new CfgCodeGenerator(this, out);
cg.go(astRoots);
}
/**
* 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

@ -10,10 +10,10 @@ public enum ExitCode {
INFINITE_LOOP(6),
DIVISION_BY_ZERO(7),
INTERNAL_ERROR(22);
public final int value;
private ExitCode(int value) {
this.value = value;
private ExitCode(int value) {
this.value = value;
}
}

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

@ -2,13 +2,14 @@ package cd.backend.codegen;
public class AssemblyFailedException extends RuntimeException {
private static final long serialVersionUID = -5658502514441032016L;
public final String assemblerOutput;
public AssemblyFailedException(
String assemblerOutput) {
super("Executing assembler failed.\n"
+ "Output:\n"
+ assemblerOutput);
this.assemblerOutput = assemblerOutput;
}
}
}

View File

@ -2,43 +2,42 @@ package cd.backend.codegen;
import cd.Config;
import cd.Main;
import cd.backend.ExitCode;
import cd.backend.codegen.RegisterManager.Register;
import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.Expr;
import cd.ir.Ast.MethodDecl;
import cd.ir.Symbol.*;
import java.io.Writer;
import java.util.*;
import java.util.Collections;
import java.util.List;
import static cd.Config.MAIN;
import static cd.Config.*;
import static cd.backend.codegen.AssemblyEmitter.constant;
import static cd.backend.codegen.AssemblyEmitter.registerOffset;
import static cd.backend.codegen.RegisterManager.BASE_REG;
import static cd.backend.codegen.RegisterManager.STACK_REG;
public class AstCodeGenerator {
/** Constant representing the boolean TRUE as integer */
static final int TRUE = 1;
/** Constant representing the boolean FALSE as integer */
static final int FALSE = 0;
/** Size of any variable in assembly
* Primitive variables take up 4 bytes (booleans are integers)
* Reference variables are a 4 byte pointer
*/
static final int VAR_SIZE = 4;
RegsNeededVisitor rnv;
ExprGenerator eg;
StmtGenerator sg;
protected RegsNeededVisitor rnv;
protected ExprGenerator eg;
protected StmtGenerator sg;
protected final Main main;
final AssemblyEmitter emit;
final RegisterManager rm = new RegisterManager();
protected final AssemblyEmitter emit;
protected final RegisterManager rm = new RegisterManager();
protected ExprGeneratorRef egRef;
protected StmtGeneratorRef sgRef;
AstCodeGenerator(Main main, Writer out) {
initMethodData();
main.allTypeSymbols = new ArrayList<>();
this.emit = new AssemblyEmitter(out);
this.main = main;
this.rnv = new RegsNeededVisitor();
@ -52,14 +51,15 @@ public class AstCodeGenerator {
}
public static AstCodeGenerator createCodeGenerator(Main main, Writer out) {
return new AstCodeGenerator(main, out);
return new AstCodeGeneratorRef(main, out);
}
/**
* Main method. Causes us to emit x86 assembly corresponding to {@code ast}
* into {@code file}. Throws a {@link RuntimeException} should any I/O error
* occur.
*
*
* <p>
* The generated file will be divided into two sections:
* <ol>
@ -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

@ -16,10 +16,10 @@ public class RegisterManager {
Register.EDI, Register.EBX};
public static final Register CALLER_SAVE[] = new Register[]{Register.EAX,
Register.ECX, Register.EDX};
// list of general purpose registers
public static final Register GPR[] = new Register[]{Register.EAX, Register.EBX,
Register.ECX, Register.EDX, Register.ESI, Register.EDI};
Register.ECX, Register.EDX, Register.ESI, Register.EDI};
// special purpose registers
public static final Register BASE_REG = Register.EBP;
@ -27,7 +27,7 @@ public class RegisterManager {
public static final int SIZEOF_REG = 4;
public enum Register {
EAX("%eax", ByteRegister.EAX), EBX("%ebx", ByteRegister.EBX), ECX(
"%ecx", ByteRegister.ECX), EDX("%edx", ByteRegister.EDX), ESI(
@ -99,18 +99,11 @@ public class RegisterManager {
return registers.remove(last);
}
public void useRegister(Register reg) {
assert registers.contains(reg);
assert reg != null;
registers.remove(reg);
}
/**
* marks a currently used register as free
*/
public void releaseRegister(Register reg) {
assert !registers.contains(reg);
assert reg != null;
registers.add(reg);
}

View File

@ -10,18 +10,19 @@ import java.util.Map;
import static java.lang.Math.max;
import static java.lang.Math.min;
/**
* Determines the maximum number of registers
* required to execute one subtree. */
/**
* Determines the maximum number of registers
* required to execute one subtree.
*/
public class RegsNeededVisitor extends AstVisitor<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
* unnecessary computation
*/
@ -31,9 +32,9 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
return memo.get(ast);
Integer res = ast.accept(this, null);
memo.put(ast, res);
return res;
return res;
}
@Override
protected Integer dflt(Ast ast, Void arg) {
// For a non-expression, it suffices to find the
@ -54,8 +55,8 @@ public class RegsNeededVisitor extends AstVisitor<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;
}
@ -64,7 +65,7 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public Integer assign(Assign ast, Void arg) {
return max(calc(ast.left()) + 1, calc(ast.right()));
}
@Override
public Integer booleanConst(BooleanConst ast, Void arg) {
return 1;
@ -74,7 +75,7 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public Integer builtInRead(BuiltInRead ast, Void arg) {
return 1;
}
@Override
public Integer cast(Cast ast, Void arg) {
return calc(ast.arg());
@ -84,7 +85,7 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public Integer index(Index ast, Void arg) {
return max(calc(ast.left()), calc(ast.right()) + 1);
}
@Override
public Integer field(Field ast, Void arg) {
return calc(ast.arg());
@ -114,7 +115,7 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public Integer thisRef(ThisRef ast, Void arg) {
return 1;
}
@Override
public Integer methodCall(MethodCallExpr ast, Void arg) {
int max = 1;
@ -134,5 +135,5 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
@Override
public Integer var(Var ast, Void arg) {
return 1;
}
}
}

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);
// Method bodies
for (Ast method : ast.methods())
visit(method, new Location(ast.sym));
return null;
}
*/
class AssignVisitor extends ExprVisitor<Void, Expr> {
@Override
public Register methodDecl(MethodDecl ast, Location arg) {
// Bookkeeping for framework
arg.enterMethod(ast.sym);
cg.initMethodData();
@Override
public Void var(Var ast, Expr right) {
final Register rhsReg = cgRef.eg.gen(right);
cgRef.emit.emitStore(rhsReg, ast.sym.offset, BASE_REG);
cgRef.rm.releaseRegister(rhsReg);
return null;
}
// Begin method
cg.emit.emitLabel(Label.method(arg.classSym(), ast.sym));
@Override
public Void field(Field ast, Expr right) {
final Register rhsReg = cgRef.eg.gen(right);
Pair<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;
}
// 1. Save and update the base register, reserve space for local variables
cg.emit.emit("enter", ast.sym.locals.size() * VAR_SIZE, 0);
for (int i = 0; i < ast.sym.locals.size(); i++)
cg.emit.emitStore(0, -(i + 1) * VAR_SIZE, BASE_REG);
@Override
public Void index(Index ast, Expr right) {
boolean isNotNull = cfgCg.check.isNotNull(ast.left());
boolean emitBoundCheck = cfgCg.check.checkArrayBound(ast);
Register rhsReg = cgRef.egRef.gen(right);
// 2. Save CPU registers
Register[] regs = RegisterManager.CALLEE_SAVE;
for (int i = 0; i < regs.length; i++)
cg.emit.emit("push", regs[i]);
Pair<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);
}
// 3. Run the code contained in the function
Register returnReg = visit(ast.body(), arg);
cg.emit.emitLabel(Label.returnMethod(arg.classSym(), ast.sym));
regs = cgRef.egRef.genPushing(arrReg, ast.right());
arrReg = regs.a;
Register idxReg = regs.b;
// 4. Restore saved registers (if there is a mismatch, the stack will be corrupted)
for (int i = regs.length - 1; i >= 0; i--)
cg.emit.emit("pop", regs[i]);
// Check array bounds
if (emitBoundCheck) {
int padding = cgRef.emitCallPrefix(null, 2);
cgRef.push(idxReg.repr);
cgRef.push(arrReg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_BOUNDS);
cgRef.emitCallSuffix(null, 2, padding);
}
// 5 and 6: Restore the old base pointer and return from the function
cg.emitMethodSuffix(returnReg == null);
cgRef.emit.emitMove(rhsReg, arrayAddress(arrReg, idxReg));
cgRef.rm.releaseRegister(arrReg);
cgRef.rm.releaseRegister(idxReg);
cgRef.rm.releaseRegister(rhsReg);
// Framework bookkeeping
arg.leaveMethod();
return null;
}
return null;
}
@Override
protected Void dfltExpr(Expr ast, Expr arg) {
throw new RuntimeException("Store to unexpected lvalue " + ast);
}
@Override
public Register ifElse(IfElse ast, Location arg) {
// Emit condition check
Register conditionRegister = cg.eg.visit(ast.condition(), arg);
// If both blocks are empty, no more code need be generated, and the condition can be ignored
if (ast.then().children().isEmpty() && ast.otherwise().children().isEmpty()) {
// Generate the condition and ignore the result
cg.rm.releaseRegister(conditionRegister);
return null;
}
String notIfLabel = cg.emit.uniqueLabel();
String endLabel = cg.emit.uniqueLabel();
cg.emit.emit("cmp", TRUE, conditionRegister);
cg.rm.releaseRegister(conditionRegister);
if (!ast.then().children().isEmpty()) {
cg.emit.emit("jne", notIfLabel);
visit(ast.then(), arg);
// If there is no otherwise, the jump instruction makes no sense
// as the next code executed will be the next instruction
if (!ast.otherwise().children().isEmpty())
cg.emit.emit("jmp", endLabel);
} else {
// No if, therefore the else follows the condition immediately
cg.emit.emit("je", endLabel);
}
cg.emit.emitLabel(notIfLabel);
// Emit otherwise
visit(ast.otherwise(), arg);
cg.emit.emitLabel(endLabel);
// Check if the ifElse ast node contains a return statement
ReturnCheckerVisitor rc = new ReturnCheckerVisitor();
if (rc.ifElse(ast, null)) {
return Register.EAX;
} else {
return null;
}
}
@Override
public Register whileLoop(WhileLoop ast, Location arg) {
String conditionLabel = cg.emit.uniqueLabel();
cg.emit.emitLabel(conditionLabel);
Register conditionRegister = cg.eg.visit(ast.condition(), arg);
cg.emit.emit("cmp", TRUE, conditionRegister);
cg.rm.releaseRegister(conditionRegister);
if (ast.body().children().isEmpty()) {
// Jump structure can be easier if there is no body
cg.emit.emit("je", conditionLabel);
} else {
String endLabel = cg.emit.uniqueLabel();
// Emit jumps, labels and body
cg.emit.emit("jne", endLabel);
visit(ast.body(), arg);
cg.emit.emit("jmp", conditionLabel);
cg.emit.emitLabel(endLabel);
}
return null;
}
@Override
public Register assign(Assign ast, Location arg) {
Register value = cg.eg.visit(ast.right(), arg);
// If the type is a reference, visiting the lhs will yield its address
// else, the lhs will yield the current value. We need a visitor that
// returns a pointer to the position of the variable/field in memory
// for primitive types.
Register pointer = cg.eg.visit(ast.left(), arg.obtainReference());
if (ast.left().type.isReferenceType()) {
// Check null pointer
String validPointerLabel = cg.emit.uniqueLabel();
cg.emit.emit("cmp", 0, pointer);
cg.emit.emit("jne", validPointerLabel);
Interrupts.exit(cg, ExitCode.NULL_POINTER);
cg.emit.emitLabel(validPointerLabel);
}
cg.emit.emitStore(value, 0, pointer);
cg.rm.releaseRegister(pointer);
cg.rm.releaseRegister(value);
return null;
}
@Override
public Register builtInWrite(BuiltInWrite ast, Location arg) {
Register reg = cg.eg.visit(ast.arg(), arg);
cg.emit.emit("sub", 16, STACK_REG);
cg.emit.emitStore(reg, 4, STACK_REG);
cg.emit.emitStore("$STR_D", 0, STACK_REG);
cg.emit.emit("call", Config.PRINTF);
cg.emit.emit("add", 16, STACK_REG);
cg.rm.releaseRegister(reg);
return null;
}
@Override
public Register builtInWriteln(BuiltInWriteln ast, Location arg) {
cg.emit.emit("sub", 16, STACK_REG);
cg.emit.emitStore("$STR_NL", 0, STACK_REG);
cg.emit.emit("call", Config.PRINTF);
cg.emit.emit("add", 16, STACK_REG);
return null;
}
@Override
public Register returnStmt(ReturnStmt ast, Location arg) {
if (ast.arg() != null) {
Register retReg = cg.eg.visit(ast.arg(), arg);
cg.emit.emitMove(retReg, Register.EAX);
cg.rm.releaseRegister(retReg);
}
cg.emit.emit("jmp", Label.returnMethod(arg.classSym(), arg.methodSym()));
return Register.EAX;
}
@Override
public Register seq(Seq ast, Location arg) {
for (Ast instruction : ast.children()) {
Register res = visit(instruction, arg);
if (res != null)
return res; // don't generate instructions after a return
}
new AssignVisitor().visit(ast.left(), ast.right());
return null;
}
}

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

@ -13,13 +13,13 @@ import java.util.HashSet;
import java.util.Set;
public class InheritanceChecker extends AstVisitor<Void, Void> {
ClassSymbol classSym;
@Override
public Void classDecl(ClassDecl ast, Void arg) {
classSym = ast.sym;
// check for cycles in the inheritance hierarchy:
Set<ClassSymbol> supers = new HashSet<ClassSymbol>();
ClassSymbol sc = classSym.superClass;
@ -28,30 +28,31 @@ public class InheritanceChecker extends AstVisitor<Void, Void> {
if (supers.contains(sc))
throw new SemanticFailure(
Cause.CIRCULAR_INHERITANCE,
"Class %s has %s as a superclass twice",
"Class %s has %s as a superclass twice",
ast.name, sc.name);
supers.add(sc);
sc = sc.superClass;
}
this.visitChildren(ast, null);
return null;
}
@Override
public Void methodDecl(MethodDecl ast, Void arg) {
// check that methods overridden from a parent class agree
// on number/type of parameters
MethodSymbol sym = ast.sym;
MethodSymbol superSym = classSym.superClass.getMethod(ast.name);
sym.overrides = superSym;
if (superSym != null) {
if (superSym.parameters.size() != sym.parameters.size())
throw new SemanticFailure(
Cause.INVALID_OVERRIDE,
"Overridden method %s has %d parameters, " +
"but original has %d",
"but original has %d",
ast.name, sym.parameters.size(),
superSym.parameters.size());
for (Pair<VariableSymbol> pair : Pair.zip(sym.parameters, superSym.parameters))
@ -59,17 +60,17 @@ 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);
}
return null;
}
}
}

View File

@ -5,22 +5,21 @@ import cd.ir.Ast.*;
import cd.ir.AstVisitor;
/**
* Visitor that checks if all paths of a given sequence have a
* return statement.
*
* <p>
* This visitor only needs to be used if are not using the Control Flow Graph.
*
*
* @author Leo Buttiker
*/
public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
@Override
protected Boolean dfltStmt(Stmt ast, Void arg) {
return false;
}
@Override
public Boolean returnStmt(ReturnStmt ast, Void arg) {
return true;
@ -28,7 +27,7 @@ public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
@Override
public Boolean ifElse(IfElse ast, Void arg) {
boolean allPathHaveAReturnStmt = true;
boolean allPathHaveAReturnStmt = true;
allPathHaveAReturnStmt &= visit(ast.then(), null);
allPathHaveAReturnStmt &= visit(ast.otherwise(), null);
return allPathHaveAReturnStmt;
@ -36,17 +35,17 @@ public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
@Override
public Boolean seq(Seq ast, Void arg) {
boolean allPathHaveAReturnStmt = false;
boolean allPathHaveAReturnStmt = false;
for (Ast child : ast.children()) {
allPathHaveAReturnStmt |= this.visit(child, null);
}
return allPathHaveAReturnStmt;
}
@Override
public Boolean whileLoop(WhileLoop ast, Void arg) {
return false;
return false;
}
}

View File

@ -11,70 +11,76 @@ import java.util.ArrayList;
import java.util.List;
public class SemanticAnalyzer {
public final Main main;
public SemanticAnalyzer(Main main) {
this.main = main;
}
public void check(List<ClassDecl> classDecls)
throws SemanticFailure {
public void check(List<ClassDecl> classDecls)
throws SemanticFailure {
{
SymTable<TypeSymbol> typeSymbols = createSymbols(classDecls);
checkInheritance(classDecls);
checkStartPoint(typeSymbols);
checkMethodBodies(typeSymbols, classDecls);
{
rewriteMethodBodies(classDecls);
main.allTypeSymbols = typeSymbols.allSymbols();
}
}
}
/**
* Creates a symbol table with symbols for all built-in types,
* Creates a symbol table with symbols for all built-in types,
* as well as all classes and their fields and methods. Also
* creates a corresponding array symbol for every type
* creates a corresponding array symbol for every type
* (named {@code type[]}).
*
* @see SymbolCreator
*/
private SymTable<TypeSymbol> createSymbols(List<ClassDecl> classDecls) {
// Start by creating a symbol for all built-in types.
SymTable<TypeSymbol> typeSymbols = new SymTable<TypeSymbol>(null);
typeSymbols.add(PrimitiveTypeSymbol.intType);
typeSymbols.add(PrimitiveTypeSymbol.booleanType);
typeSymbols.add(PrimitiveTypeSymbol.voidType);
typeSymbols.add(ClassSymbol.objectType);
// Add symbols for all declared classes.
for (ClassDecl ast : classDecls) {
// Check for classes named Object
if (ast.name.equals(ClassSymbol.objectType.name))
throw new SemanticFailure(Cause.OBJECT_CLASS_DEFINED);
ast.sym = new ClassSymbol(ast);
typeSymbols.add(ast.sym);
typeSymbols.add(ast.sym);
}
// Create symbols for arrays of each type.
for (Symbol sym : new ArrayList<Symbol>(typeSymbols.localSymbols())) {
Symbol.ArrayTypeSymbol array =
new Symbol.ArrayTypeSymbol((TypeSymbol) sym);
Symbol.ArrayTypeSymbol array =
new Symbol.ArrayTypeSymbol((TypeSymbol) sym);
typeSymbols.add(array);
}
// For each class, create symbols for each method and field
SymbolCreator sc = new SymbolCreator(main, typeSymbols);
for (ClassDecl ast : classDecls)
sc.createSymbols(ast);
return typeSymbols;
}
/**
* Check for errors related to inheritance:
* Check for errors related to inheritance:
* circular inheritance, invalid super
* classes, methods with different types, etc.
* Note that this must be run early because other code assumes
* that the inheritance is correct, for type checking etc.
*
* @see InheritanceChecker
*/
private void checkInheritance(List<ClassDecl> classDecls) {
@ -93,25 +99,26 @@ public class SemanticAnalyzer {
MethodSymbol mainMethod = cs.getMethod("main");
if (mainMethod != null && mainMethod.parameters.size() == 0 &&
mainMethod.returnType == PrimitiveTypeSymbol.voidType) {
main.mainType = cs;
return; // found the main() method!
}
}
throw new SemanticFailure(Cause.INVALID_START_POINT, "No Main class with method 'void main()' found");
}
/**
* Check the bodies of methods for errors, particularly type errors
* but also undefined identifiers and the like.
*
* @see TypeChecker
*/
private void checkMethodBodies(
SymTable<TypeSymbol> typeSymbols,
List<ClassDecl> classDecls)
{
List<ClassDecl> classDecls) {
TypeChecker tc = new TypeChecker(typeSymbols);
for (ClassDecl classd : classDecls) {
SymTable<VariableSymbol> fldTable = new SymTable<VariableSymbol>(null);
// add all fields of this class, or any of its super classes
@ -119,37 +126,42 @@ public class SemanticAnalyzer {
for (VariableSymbol s : p.fields.values())
if (!fldTable.contains(s.name))
fldTable.add(s);
// type check any method bodies and final locals
for (MethodDecl md : classd.methods()) {
boolean hasReturn = new ReturnCheckerVisitor().visit(md.body(), null);
if (!md.returnType.equals("void") && !hasReturn) {
throw new SemanticFailure(Cause.MISSING_RETURN,
"Method %s.%s is missing a return statement",
classd.name,
throw new SemanticFailure(Cause.MISSING_RETURN,
"Method %s.%s is missing a return statement",
classd.name,
md.name);
}
SymTable<VariableSymbol> mthdTable = new SymTable<VariableSymbol>(fldTable);
mthdTable.add(classd.sym.thisSymbol);
for (VariableSymbol p : md.sym.parameters) {
mthdTable.add(p);
}
for (VariableSymbol l : md.sym.locals.values()) {
mthdTable.add(l);
}
tc.checkMethodDecl(md, mthdTable);
}
}
}
private void rewriteMethodBodies(List<ClassDecl> classDecls) {
for (ClassDecl cd : classDecls)
new FieldQualifier().rewrite(cd);
}
}

View File

@ -1,40 +1,50 @@
package cd.frontend.semantic;
/**
/**
* Thrown by the semantic checker when a semantic error is detected
* in the user's program. */
* in the user's program.
*/
public class SemanticFailure extends RuntimeException {
private static final long serialVersionUID = 5375946759285719123L;
public enum Cause {
/**
/**
* Caused by an assignment to either a final field, {@code this},
* or some other kind of expression which cannot be assigned to.
* <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,
/**
/**
* Can occur in many contents:
* <ul>
* <li> Assignment to a variable from an expression of wrong type
@ -53,12 +63,12 @@ public class SemanticFailure extends RuntimeException {
* </ul>
*/
TYPE_ERROR,
/**
* A class is its own super class
*/
CIRCULAR_INHERITANCE,
/**
* One of the following:
* <ul>
@ -69,13 +79,13 @@ public class SemanticFailure extends RuntimeException {
* </ul>
*/
INVALID_START_POINT,
/**
* A class {@code Object} was defined. This class is implicitly
* defined and cannot be defined explicitly.
*/
OBJECT_CLASS_DEFINED,
/**
* A type name was found for which no class declaration exists.
* This can occur in many contexts:
@ -86,34 +96,36 @@ public class SemanticFailure extends RuntimeException {
* </ul>
*/
NO_SUCH_TYPE,
/**
* The parameters of an overridden method have different types
* from the base method, there is a different
* from the base method, there is a different
* number of parameters, or the return value is different.
*/
INVALID_OVERRIDE,
/** A method was called with the wrong number of arguments */
/**
* A method was called with the wrong number of arguments
*/
WRONG_NUMBER_OF_ARGUMENTS,
/**
/**
* Indicates the use of a local variable that may not have been
* initialized (ACD only).
*/
POSSIBLY_UNINITIALIZED,
}
public final Cause cause;
public SemanticFailure(Cause cause) {
super(cause.name());
this.cause = cause;
}
public SemanticFailure(Cause cause, String format, Object... args) {
super(String.format(format, args));
this.cause = cause;
}
}

View File

@ -3,24 +3,23 @@ package cd.frontend.semantic;
import cd.frontend.semantic.SemanticFailure.Cause;
import cd.ir.Symbol;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
/**
* A simple symbol table, with a pointer to the enclosing scope.
/**
* A simple symbol table, with a pointer to the enclosing scope.
* Used by {@link TypeChecker} to store the various scopes for
* local, parameter, and field lookup. */
* local, parameter, and field lookup.
*/
public class SymTable<S extends Symbol> {
private final Map<String, S> map = new HashMap<String, S>();
private final SymTable<S> parent;
public SymTable(SymTable<S> parent) {
this.parent = parent;
}
public void add(S sym) {
// check that the symbol is not already declared *at this level*
if (containsLocally(sym.name))
@ -28,10 +27,21 @@ public class SymTable<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();
}
/**
* True if there is a declaration with the given name at any
* level in the symbol table
@ -40,7 +50,7 @@ public class SymTable<S extends Symbol> {
return get(name) != null;
}
/**
/**
* True if there is a declaration at THIS level in the symbol
* table; may return {@code false} even if a declaration exists
* in some enclosing scope
@ -49,9 +59,10 @@ public class SymTable<S extends Symbol> {
return this.map.containsKey(name);
}
/**
/**
* Base method: returns {@code null} if no symbol by that
* name can be found, in this table or in its parents */
* name can be found, in this table or in its parents
*/
public S get(String name) {
S res = map.get(name);
if (res != null)
@ -60,11 +71,12 @@ public class SymTable<S extends Symbol> {
return null;
return parent.get(name);
}
/**
* Finds the symbol with the given name, or fails with a
* Finds the symbol with the given name, or fails with a
* NO_SUCH_TYPE error. Only really makes sense to use this
* if S == TypeSymbol, but we don't strictly forbid it... */
* if S == TypeSymbol, but we don't strictly forbid it...
*/
public S getType(String name) {
S res = get(name);
if (res == null)

View File

@ -21,10 +21,10 @@ import java.util.Set;
* and local variables.
*/
public class SymbolCreator extends Object {
final Main main;
final SymTable<TypeSymbol> typesTable;
public SymbolCreator(Main main, SymTable<TypeSymbol> typesTable) {
this.main = main;
this.typesTable = typesTable;
@ -34,33 +34,33 @@ public class SymbolCreator extends Object {
// lookup the super class. the grammar guarantees that this
// will refer to a class, if the lookup succeeds.
cd.sym.superClass = (ClassSymbol) typesTable.getType(cd.superClass);
new ClassSymbolCreator(cd.sym).visitChildren(cd, null);
new ClassSymbolCreator(cd.sym).visitChildren(cd, null);
}
/**
* Useful method which adds a symbol to a map, checking to see
* Useful method which adds a symbol to a map, checking to see
* that there is not already an entry with the same name.
* If a symbol with the same name exists, throws an exception.
*/
public <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,
"Symbol '%s' was declared twice in the same scope",
"Symbol '%s' was declared twice in the same scope",
sym.name);
map.put(sym.name, sym);
}
/**
* Creates symbols for all fields/constants/methods in a class.
* Uses {@link MethodSymbolCreator} to create symbols for all
* Uses {@link MethodSymbolCreator} to create symbols for all
* parameters and local variables to each method as well.
* Checks for duplicate members.
*/
public class ClassSymbolCreator extends AstVisitor<Void, Void> {
final ClassSymbol classSym;
final ClassSymbol classSym;
public ClassSymbolCreator(ClassSymbol classSym) {
this.classSym = classSym;
}
@ -75,9 +75,11 @@ public class SymbolCreator extends Object {
@Override
public Void methodDecl(MethodDecl ast, Void arg) {
ast.sym = new MethodSymbol(ast);
ast.sym.owner = classSym;
add(classSym.methods, ast.sym);
// create return type symbol
@ -86,13 +88,13 @@ public class SymbolCreator extends Object {
} else {
ast.sym.returnType = typesTable.getType(ast.returnType);
}
// create symbols for each parameter
Set<String> pnames = new HashSet<String>();
for (int i = 0; i < ast.argumentNames.size(); i++) {
String argumentName = ast.argumentNames.get(i);
String argumentType = ast.argumentTypes.get(i);
if (pnames.contains(argumentName))
if (pnames.contains(argumentName))
throw new SemanticFailure(
Cause.DOUBLE_DECLARATION,
"Method '%s' has two parameters named '%s'",
@ -102,23 +104,23 @@ public class SymbolCreator extends Object {
argumentName, typesTable.getType(argumentType));
ast.sym.parameters.add(vs);
}
// create symbols for the local variables
new MethodSymbolCreator(ast.sym).visitChildren(ast.decls(), null);
return null;
}
}
public class MethodSymbolCreator extends AstVisitor<Void, Void> {
final MethodSymbol methodSym;
public MethodSymbolCreator(MethodSymbol methodSym) {
this.methodSym = methodSym;
}
@Override
public Void varDecl(VarDecl ast, Void arg) {
ast.sym = new VariableSymbol(

View File

@ -11,7 +11,7 @@ import cd.util.debug.AstOneLine;
public class TypeChecker {
final private SymTable<TypeSymbol> typeSymbols;
public TypeChecker(SymTable<TypeSymbol> typeSymbols) {
this.typeSymbols = typeSymbols;
}
@ -19,11 +19,12 @@ public class TypeChecker {
public void checkMethodDecl(MethodDecl method, SymTable<VariableSymbol> locals) {
new MethodDeclVisitor(method, locals).visit(method.body(), null);
}
/**
* Checks whether two expressions have the same type
* and throws an exception otherwise.
*
* @return The common type of the two expression.
*/
private TypeSymbol checkTypesEqual(Expr leftExpr, Expr rightExpr, SymTable<VariableSymbol> locals) {
@ -32,30 +33,30 @@ public class TypeChecker {
if (leftType != rightType) {
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Expected operand types to be equal but found %s, %s",
leftType,
rightType);
Cause.TYPE_ERROR,
"Expected operand types to be equal but found %s, %s",
leftType,
rightType);
}
return leftType;
}
private void checkTypeIsInt(TypeSymbol type) {
if (type != PrimitiveTypeSymbol.intType) {
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Expected %s for operands but found type %s",
PrimitiveTypeSymbol.intType,
type);
Cause.TYPE_ERROR,
"Expected %s for operands but found type %s",
PrimitiveTypeSymbol.intType,
type);
}
}
private ClassSymbol asClassSymbol(TypeSymbol type) {
if (type instanceof ClassSymbol)
return (ClassSymbol) type;
throw new SemanticFailure(
Cause.TYPE_ERROR,
Cause.TYPE_ERROR,
"A class type was required, but %s was found", type);
}
@ -63,10 +64,10 @@ public class TypeChecker {
if (type instanceof ArrayTypeSymbol)
return (ArrayTypeSymbol) type;
throw new SemanticFailure(
Cause.TYPE_ERROR,
Cause.TYPE_ERROR,
"An array type was required, but %s was found", type);
}
private TypeSymbol typeExpr(Expr expr, SymTable<VariableSymbol> locals) {
return new TypingVisitor().visit(expr, locals);
}
@ -80,9 +81,9 @@ public class TypeChecker {
expected,
actual);
}
private class MethodDeclVisitor extends AstVisitor<Void, Void> {
private MethodDecl method;
private SymTable<VariableSymbol> locals;
@ -95,7 +96,7 @@ public class TypeChecker {
protected Void dfltExpr(Expr ast, Void arg) {
throw new RuntimeException("Should not get here");
}
@Override
public Void assign(Assign ast, Void arg) {
TypeSymbol lhs = typeLhs(ast.left(), locals);
@ -113,15 +114,15 @@ public class TypeChecker {
checkType(ast.arg(), PrimitiveTypeSymbol.intType, locals);
return null;
}
@Override
public Void builtInWriteln(BuiltInWriteln ast, Void arg) {
return null;
}
@Override
public Void ifElse(IfElse ast, Void arg) {
checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
checkType((Expr) ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
visit(ast.then(), arg);
if (ast.otherwise() != null)
visit(ast.otherwise(), arg);
@ -131,17 +132,17 @@ public class TypeChecker {
@Override
public Void methodCall(MethodCall ast, Void arg) {
typeExpr(ast.getMethodCallExpr(), locals);
return null;
}
@Override
public Void whileLoop(WhileLoop ast, Void arg) {
checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
checkType((Expr) ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
return visit(ast.body(), arg);
}
@Override
public Void returnStmt(ReturnStmt ast, Void arg) {
boolean hasArg = ast.arg() != null;
@ -150,11 +151,11 @@ public class TypeChecker {
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Return statement of method with void return type should "
+ "not have arguments.");
+ "not have arguments.");
} else if (!hasArg) {
// X m() { return; }
if (method.sym.returnType != PrimitiveTypeSymbol.voidType) {
throw new SemanticFailure(
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Return statement has no arguments. Expected %s but type was %s",
method.sym.returnType,
@ -164,58 +165,58 @@ public class TypeChecker {
// X m() { return y; }
checkType(ast.arg(), method.sym.returnType, locals);
}
return null;
}
}
private class TypingVisitor extends ExprVisitor<TypeSymbol, SymTable<VariableSymbol>> {
@Override
public TypeSymbol visit(Expr ast, SymTable<VariableSymbol> arg) {
ast.type = super.visit(ast, arg);
return ast.type;
}
@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_AND:
case B_OR:
checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals);
checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
case B_EQUAL:
case B_NOT_EQUAL:
TypeSymbol left = typeExpr(ast.left(), locals);
TypeSymbol right = typeExpr(ast.right(), locals);
if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left))
return PrimitiveTypeSymbol.booleanType;
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Types %s and %s could never be equal",
left, right);
case B_LESS_THAN:
case B_LESS_OR_EQUAL:
case B_GREATER_THAN:
case B_GREATER_OR_EQUAL:
checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals));
return PrimitiveTypeSymbol.booleanType;
case B_TIMES:
case B_DIV:
case B_MOD:
case B_PLUS:
case B_MINUS:
TypeSymbol type = checkTypesEqual(ast.left(), ast.right(), locals);
checkTypeIsInt(type);
return type;
case B_AND:
case B_OR:
checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals);
checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
case B_EQUAL:
case B_NOT_EQUAL:
TypeSymbol left = typeExpr(ast.left(), locals);
TypeSymbol right = typeExpr(ast.right(), locals);
if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left))
return PrimitiveTypeSymbol.booleanType;
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Types %s and %s could never be equal",
left, right);
case B_LESS_THAN:
case B_LESS_OR_EQUAL:
case B_GREATER_THAN:
case B_GREATER_OR_EQUAL:
checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals));
return PrimitiveTypeSymbol.booleanType;
}
throw new RuntimeException("Unhandled operator "+ast.operator);
throw new RuntimeException("Unhandled operator " + ast.operator);
}
@Override
@ -232,10 +233,10 @@ public class TypeChecker {
public TypeSymbol cast(Cast ast, SymTable<VariableSymbol> locals) {
TypeSymbol argType = typeExpr(ast.arg(), locals);
ast.type = typeSymbols.getType(ast.typeName);
if (argType.isSuperTypeOf(ast.type) || ast.type.isSuperTypeOf(argType))
return ast.type;
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Types %s and %s in cast are completely unrelated.",
@ -295,21 +296,20 @@ public class TypeChecker {
@Override
public TypeSymbol unaryOp(UnaryOp ast, SymTable<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
@ -322,20 +322,20 @@ public class TypeChecker {
ast.setSymbol(locals.get(ast.name));
return ast.sym.type;
}
@Override
public TypeSymbol methodCall(MethodCallExpr ast, SymTable<VariableSymbol> locals) {
ClassSymbol rcvrType = asClassSymbol(typeExpr(ast.receiver(), locals));
ClassSymbol rcvrType = asClassSymbol(typeExpr(ast.receiver(), locals));
MethodSymbol mthd = rcvrType.getMethod(ast.methodName);
if (mthd == null)
throw new SemanticFailure(
Cause.NO_SUCH_METHOD,
"Class %s has no method %s()",
rcvrType.name, ast.methodName);
rcvrType.name, ast.methodName);
ast.sym = mthd;
// Check that the number of arguments is correct.
if (ast.argumentsWithoutReceiver().size() != mthd.parameters.size())
throw new SemanticFailure(
@ -344,33 +344,36 @@ public class TypeChecker {
ast.methodName,
mthd.parameters.size(),
ast.argumentsWithoutReceiver().size());
// Check that the arguments are of correct type.
int i = 0;
for (Ast argAst : ast.argumentsWithoutReceiver())
checkType((Expr)argAst, mthd.parameters.get(i++).type, locals);
checkType((Expr) argAst, mthd.parameters.get(i++).type, locals);
return ast.sym.returnType;
}
}
/**
* Checks an expr as the left-hand-side of an assignment,
* returning the type of value that may be assigned there.
* May fail if the expression is not a valid LHS (for example,
* a "final" field). */
* a "final" field).
*/
private TypeSymbol typeLhs(Expr expr, SymTable<VariableSymbol> locals) {
return new LValueVisitor().visit(expr, locals);
}
/**
* @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) {
return typeExpr(ast, locals);
@ -385,13 +388,15 @@ public class TypeChecker {
public TypeSymbol var(Var ast, SymTable<VariableSymbol> locals) {
return typeExpr(ast, locals);
}
/** Any other kind of expression is not a value lvalue */
/**
* Any other kind of expression is not a value lvalue
*/
@Override
protected TypeSymbol dfltExpr(Expr ast, SymTable<VariableSymbol> locals) {
throw new SemanticFailure(
Cause.NOT_ASSIGNABLE,
"'%s' is not a valid lvalue",
"'%s' is not a valid lvalue",
AstOneLine.toString(ast));
}
}

File diff suppressed because it is too large Load Diff

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

@ -19,13 +19,14 @@ public class AstRewriteVisitor<A> extends AstVisitor<Ast, A> {
}
return ast;
}
/**
* This method is called when a node is replaced. Subclasses can override it to do some
* bookkeeping.
* <p>
* The default implementation does nothing.
*/
protected void nodeReplaced(Ast oldNode, Ast newNode) {}
protected void nodeReplaced(Ast oldNode, Ast newNode) {
}
}

View File

@ -4,18 +4,21 @@ import cd.ir.Ast.Decl;
import cd.ir.Ast.Expr;
import cd.ir.Ast.Stmt;
/** A visitor that visits any kind of node */
public class AstVisitor<R,A> extends ExprVisitor<R,A> {
/**
* Recurse and process {@code ast}. It is preferred to
/**
* 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. */
* it can be overloaded to introduce memoization,
* for example.
*/
public R visit(Ast ast, A arg) {
return ast.accept(this, arg);
}
/**
* Overrides {@link ExprVisitor#visitChildren(Expr, Object)} and
* delegates to the more general {@link #visitChildren(Ast, Object)}
@ -26,11 +29,11 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
public final R visitChildren(Expr ast, A arg) {
return visitChildren((Ast) ast, arg);
}
/**
* A handy function which visits the children of {@code ast},
* providing "arg" to each of them. It returns the result of
* the last child in the list. It is invoked by the method
* the last child in the list. It is invoked by the method
* {@link #dflt(Ast, Object)} by default.
*/
public R visitChildren(Ast ast, A arg) {
@ -39,35 +42,39 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
lastValue = visit(child, arg);
return lastValue;
}
/**
/**
* The default action for default actions is to call this,
* which simply recurses to any children. Also called
* by seq() by default. */
* by seq() by default.
*/
protected R dflt(Ast ast, A arg) {
return visitChildren(ast, arg);
}
/**
* The default action for statements is to call this */
/**
* The default action for statements is to call this
*/
protected R dfltStmt(Stmt ast, A arg) {
return dflt(ast, arg);
}
/**
* The default action for expressions is to call this */
/**
* The default action for expressions is to call this
*/
@Override
protected R dfltExpr(Expr ast, A arg) {
return dflt(ast, arg);
}
/**
/**
* The default action for AST nodes representing declarations
* is to call this function */
* is to call this function
*/
protected R dfltDecl(Decl ast, A arg) {
return dflt(ast, arg);
}
public R assign(Ast.Assign ast, A arg) {
return dfltStmt(ast, arg);
}
@ -79,23 +86,23 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
public R builtInWriteln(Ast.BuiltInWriteln ast, A arg) {
return dfltStmt(ast, arg);
}
public R classDecl(Ast.ClassDecl ast, A arg) {
return dfltDecl(ast, arg);
}
public R methodDecl(Ast.MethodDecl ast, A arg) {
return dfltDecl(ast, arg);
}
public R varDecl(Ast.VarDecl ast, A arg) {
return dfltDecl(ast, arg);
}
public R ifElse(Ast.IfElse ast, A arg) {
return dfltStmt(ast, arg);
}
public R returnStmt(Ast.ReturnStmt ast, A arg) {
return dfltStmt(ast, arg);
}
@ -107,11 +114,11 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
public R nop(Ast.Nop ast, A arg) {
return dfltStmt(ast, arg);
}
public R seq(Ast.Seq ast, A arg) {
return dflt(ast, arg);
}
public R whileLoop(Ast.WhileLoop ast, A arg) {
return dfltStmt(ast, arg);
}

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,16 +5,17 @@ import cd.ir.Ast.Expr;
/**
* A visitor that only visits {@link Expr} nodes.
*/
public class ExprVisitor<R,A> {
/**
* Recurse and process {@code ast}. It is preferred to
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. */
* it can be overloaded to introduce memoization,
* for example.
*/
public R visit(Expr ast, A arg) {
return ast.accept(this, arg);
}
/**
* Visits all children of the expression. Relies on the fact
* that {@link Expr} nodes only contain other {@link Expr} nodes.
@ -22,18 +23,19 @@ public class ExprVisitor<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);
}
public R binaryOp(Ast.BinaryOp ast, A arg) {
return dfltExpr(ast, arg);
}
@ -41,15 +43,15 @@ public class ExprVisitor<R,A> {
public R booleanConst(Ast.BooleanConst ast, A arg) {
return dfltExpr(ast, arg);
}
public R builtInRead(Ast.BuiltInRead ast, A arg) {
return dfltExpr(ast, arg);
}
public R cast(Ast.Cast ast, A arg) {
return dfltExpr(ast, arg);
}
public R field(Ast.Field ast, A arg) {
return dfltExpr(ast, arg);
}
@ -61,7 +63,7 @@ public class ExprVisitor<R,A> {
public R intConst(Ast.IntConst ast, A arg) {
return dfltExpr(ast, arg);
}
public R methodCall(Ast.MethodCallExpr ast, A arg) {
return dfltExpr(ast, arg);
}
@ -73,7 +75,7 @@ public class ExprVisitor<R,A> {
public R newArray(Ast.NewArray ast, A arg) {
return dfltExpr(ast, arg);
}
public R nullConst(Ast.NullConst ast, A arg) {
return dfltExpr(ast, arg);
}

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