├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── .gitmodules ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── rt └── runtime.c ├── settings.gradle └── src └── main ├── jacc └── Decaf.jacc ├── java └── decaf │ ├── Main.java │ ├── backend │ ├── asm │ │ ├── Asm.java │ │ ├── AsmEmitter.java │ │ ├── SubroutineEmitter.java │ │ ├── SubroutineInfo.java │ │ ├── mips │ │ │ ├── MipsAsmEmitter.java │ │ │ └── MipsSubroutineEmitter.java │ │ └── x86 │ │ │ ├── X86AsmEmitter.java │ │ │ └── X86SubroutineEmitter.java │ ├── dataflow │ │ ├── BasicBlock.java │ │ ├── CFG.java │ │ ├── CFGBuilder.java │ │ ├── LivenessAnalyzer.java │ │ └── Loc.java │ ├── opt │ │ └── Optimizer.java │ └── reg │ │ ├── BruteRegAlloc.java │ │ └── RegAlloc.java │ ├── driver │ ├── Config.java │ ├── ErrorIssuer.java │ ├── Launcher.java │ ├── OptParser.java │ ├── Phase.java │ ├── Task.java │ ├── TaskFactory.java │ └── error │ │ ├── BadArgCountError.java │ │ ├── BadArgTypeError.java │ │ ├── BadArrElementError.java │ │ ├── BadEscCharError.java │ │ ├── BadInheritanceError.java │ │ ├── BadLengthArgError.java │ │ ├── BadLengthError.java │ │ ├── BadNewArrayLength.java │ │ ├── BadOverrideError.java │ │ ├── BadPrintArgError.java │ │ ├── BadReturnTypeError.java │ │ ├── BadTestExpr.java │ │ ├── BadVarTypeError.java │ │ ├── BreakOutOfLoopError.java │ │ ├── ClassNotFoundError.java │ │ ├── DecafError.java │ │ ├── DeclConflictError.java │ │ ├── FieldNotAccessError.java │ │ ├── FieldNotFoundError.java │ │ ├── IncompatBinOpError.java │ │ ├── IncompatUnOpError.java │ │ ├── IntTooLargeError.java │ │ ├── MissingReturnError.java │ │ ├── MsgError.java │ │ ├── NewlineInStrError.java │ │ ├── NoMainClassError.java │ │ ├── NotArrayError.java │ │ ├── NotClassError.java │ │ ├── NotClassFieldError.java │ │ ├── NotClassMethodError.java │ │ ├── OverridingVarError.java │ │ ├── RefNonStaticError.java │ │ ├── SubNotIntError.java │ │ ├── ThisInStaticFuncError.java │ │ ├── UndeclVarError.java │ │ ├── UnrecogCharError.java │ │ └── UntermStrError.java │ ├── frontend │ ├── parsing │ │ ├── AbstractLexer.java │ │ ├── AbstractParser.java │ │ ├── JaccParser.java │ │ ├── LLParser.java │ │ ├── SemValue.java │ │ └── Tokens.java │ ├── scope │ │ ├── ClassScope.java │ │ ├── FormalScope.java │ │ ├── GlobalScope.java │ │ ├── LocalScope.java │ │ ├── Scope.java │ │ └── ScopeStack.java │ ├── symbol │ │ ├── ClassSymbol.java │ │ ├── MethodSymbol.java │ │ ├── Symbol.java │ │ └── VarSymbol.java │ ├── tacgen │ │ ├── TacEmitter.java │ │ └── TacGen.java │ ├── tree │ │ ├── Pos.java │ │ ├── Tree.java │ │ ├── TreeNode.java │ │ └── Visitor.java │ ├── type │ │ ├── ArrayType.java │ │ ├── BuiltInType.java │ │ ├── ClassType.java │ │ ├── FunType.java │ │ └── Type.java │ └── typecheck │ │ ├── Namer.java │ │ ├── TypeLitVisited.java │ │ └── Typer.java │ ├── lowlevel │ ├── AsmCodePrinter.java │ ├── Mips.java │ ├── StringUtils.java │ ├── X86.java │ ├── instr │ │ ├── HoleInstr.java │ │ ├── NativeInstr.java │ │ ├── PseudoInstr.java │ │ ├── Reg.java │ │ └── Temp.java │ ├── label │ │ ├── FuncLabel.java │ │ ├── IntrinsicLabel.java │ │ ├── Label.java │ │ └── VTableLabel.java │ ├── log │ │ ├── ColorConsoleHandler.java │ │ ├── IndentPrinter.java │ │ └── Log.java │ └── tac │ │ ├── ClassInfo.java │ │ ├── FuncVisitor.java │ │ ├── Intrinsic.java │ │ ├── Opcodes.java │ │ ├── ProgramWriter.java │ │ ├── RuntimeError.java │ │ ├── Simulator.java │ │ ├── StringPool.java │ │ ├── TacFunc.java │ │ ├── TacInstr.java │ │ ├── TacProg.java │ │ └── VTable.java │ └── printing │ ├── PrettyCFG.java │ ├── PrettyPrinter.java │ ├── PrettyScope.java │ └── PrettyTree.java ├── jflex └── Decaf.jflex └── ll1pg └── Decaf.spec /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 14 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: '14-ea' 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build with Gradle 26 | run: ./gradlew build 27 | - name: Run tests 28 | run: | 29 | git submodule update --init --recursive 30 | cd tests 31 | ./run-test 1a 32 | ./run-test 1b | grep 'Pass/Total: 16/17' 33 | # pa1b has one failing test case that is students' job. 34 | ./run-test 2 35 | ./run-test 3 36 | ./run-test 5-x86 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea/ 3 | build/ 4 | 5 | *.class 6 | 7 | # Mobile Tools for Java (J2ME) 8 | .mtj.tmp/ 9 | 10 | # Package Files # 11 | *.jar 12 | *.war 13 | *.ear 14 | 15 | # Ignore Gradle GUI config 16 | gradle-app.setting 17 | 18 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 19 | !gradle-wrapper.jar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | *.DS_Store 25 | 26 | TestCases/ 27 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests"] 2 | path = tests 3 | url = https://github.com/decaf-lang/decaf-tests.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The New Decaf Compiler 2 | 3 | 4 | 5 | [![](https://github.com/decaf-lang/decaf/workflows/Java%20CI%20with%20Gradle/badge.svg?branch=master)](https://github.com/decaf-lang/decaf/actions) 6 | 7 | Decaf is a Java-like, but much smaller programming language mainly for educational purpose. 8 | We now have at least three different implementations of the compiler in Java, Scala and Rust. 9 | Since the standard language has quite a limited set of language features, students are welcome to add their own new features. 10 | 11 | ## Getting Started 12 | 13 | This project requires 14 | * JDK 14 15 | * Gradle 6.5.1 16 | 17 | Other dependencies will be automatically downloaded from the maven central repository by the build script. 18 | 19 | After git clone, you need to setup submodules by 20 | ``` 21 | git submodule update --recursive --init 22 | ``` 23 | 24 | ## Build 25 | 26 | Type the standard Gradle build command in your CLI: 27 | 28 | ```sh 29 | gradle build 30 | ``` 31 | 32 | The built jar will be located at `build/libs/decaf.jar`. 33 | 34 | Or, import the project in a Java IDE (like IDEA or Eclipse, or your favorite VS Code) and use gradle plugin, if available. 35 | 36 | ## Run 37 | 38 | In your CLI, type 39 | 40 | ```sh 41 | java -jar build/libs/decaf.jar -h 42 | ``` 43 | 44 | to display the usage help. 45 | 46 | Possible targets/tasks are: 47 | 48 | - PA1: parse source code and output the pretty printed tree, or error messages 49 | - PA1-LL: like PA1, but use hand-coded LL parsing algorithm, with the help of a LL table generator [ll1pg](https://github.com/paulzfm/ll1pg) 50 | - PA2: type check and output the pretty printed scopes, or error messages 51 | - PA3: generate TAC (three-address code), dump it to a .tac file, and then output the execution result using our built-in simulator 52 | - PA4: currently same with PA3, will be reserved for students to do a bunch of optimizations on TAC 53 | - PA5: (default target) allocate registers and emit assembly code, currently we are using a very brute-force algorithm and only generates MIPS assembly code (with pseudo-ops, and no delayed branches) 54 | 55 | To run the MIPS assembly code, you may need [spim](http://spimsimulator.sourceforge.net), a MIPS32 simulator. 56 | For Mac OS users, simply install `spim` with `brew install spim` and run with `spim -file your_file.s`. 57 | 58 | ## Releases 59 | 60 | See https://github.com/decaf-lang/decaf/releases for releases, including separate frameworks for PA1 -- PA3. 61 | 62 | ## Materials 63 | 64 | We have a couple of Chinese documents on the language specification and implementation outlines: 65 | 66 | - https://decaf-lang.gitbook.io 67 | - https://decaf-project.gitbook.io 68 | 69 | ## Development & Contribution 70 | 71 | In future, we will develop on (possibly variates of) development branches, 72 | and only merge release versions into the master branch. 73 | 74 | Issues and pull requests for fixing bugs are welcome. However, adding new language features will not be considered, because that's students' work! 75 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.xbib.gradle.plugin.jflex' version '1.2.0' 4 | id 'org.xbib.gradle.plugin.jacc' version '1.1.3' 5 | id 'de.undercouch.download' version "4.0.0" 6 | } 7 | 8 | group 'decaf' 9 | 10 | sourceCompatibility = JavaVersion.VERSION_14 11 | 12 | sourceSets.main.java.srcDirs += files("$project.buildDir/generated-src/ll1pg") 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | // https://mvnrepository.com/artifact/commons-cli/commons-cli 20 | implementation group: 'commons-cli', name: 'commons-cli', version: '1.4' 21 | 22 | // https://mvnrepository.com/artifact/commons-io/commons-io 23 | implementation group: 'commons-io', name: 'commons-io', version: '2.6' 24 | 25 | // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 26 | implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.9' 27 | } 28 | 29 | task ll1pg(type: Task) { 30 | download { 31 | src 'https://github.com/paulzfm/ll1pg/releases/download/decaf-course-19-fall/ll1pg-decaf-course-19-fall.jar' 32 | dest "$rootDir/ll1pg.jar" 33 | overwrite false 34 | } 35 | 36 | ConfigurableFileTree fs = fileTree(dir: file('src/main/ll1pg'), include: '**/*.spec') 37 | File dstDir = file("$project.buildDir/generated-src/ll1pg") 38 | delete(dstDir) 39 | mkdir(dstDir) 40 | 41 | if (fs.empty) System.err.println("No *.spec file found in 'src/main/ll1pg/', skip") 42 | fs.visit { FileVisitDetails f -> 43 | if (f.isDirectory()) return 44 | javaexec { 45 | classpath = files("$rootDir/ll1pg.jar") 46 | main = 'decaf.tools.ll1pg.Main' 47 | args = [f.file.path, dstDir.path] 48 | } 49 | } 50 | } 51 | 52 | tasks.compileJava.dependsOn tasks.ll1pg 53 | 54 | jar { 55 | manifest { 56 | attributes 'Main-Class': 'decaf.Main' 57 | } 58 | 59 | // Create a fat JAR with all the dependencies. 60 | from { 61 | configurations.runtimeClasspath.collect { 62 | it.isDirectory() ? it : zipTree(it) 63 | } 64 | } 65 | } 66 | 67 | javadoc { 68 | // Ignore the auto-generated file, because their javadoc comments may be out-dated (e.g. jflex ). 69 | source = sourceSets.main.allJava.filter { !it.toString().contains('generated-src') } 70 | 71 | options.source('14') 72 | // NOTE: the first '-' is added by Gradle and thus ignored. 73 | // See https://github.com/gradle/gradle/issues/2354 74 | options.showFromPrivate() 75 | } 76 | 77 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decaf-lang/decaf/3362432d12fc73af6c4200e1c5db3a36a578cbf5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /rt/runtime.c: -------------------------------------------------------------------------------- 1 | // These are various functions called by decaf programs. 2 | // 3 | // For MIPS their instructions are hardcoded, too bad. 4 | // 5 | // For x86-32 (i386), we implement the functions as a language runtime in C, 6 | // and everything work as below: 7 | // 8 | // +--------------------+ +------------------+ 9 | // | Your Decaf Program | | language runtime | 10 | // +---------+----------+ +---------+--------+ 11 | // | | 12 | // | The decaf compiler | gcc -m32 -S 13 | // | | 14 | // | | 15 | // v v 16 | // +--------------------+ +------------------+ 17 | // | i386 assembly | | i386 assembly | 18 | // +---------------\----+ +---/--------------+ 19 | // \ / 20 | // \ / 21 | // \ / gcc -m32 22 | // \ / (actually invoking the assembler `as` 23 | // \ / and the linker `ld`) 24 | // v v 25 | // +----------------------------+ 26 | // | i386 executable code | 27 | // +-------------+--------------+ 28 | // | 29 | // | run by your PC 30 | // | 31 | // v 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | 39 | void _PrintInt(int a) { 40 | printf("%d", a); 41 | } 42 | 43 | void _PrintString(const char *a) { 44 | printf("%s", a); 45 | } 46 | 47 | void _PrintBool(int x) { 48 | printf("%s", x ? "true" : "false"); 49 | } 50 | 51 | void* _Alloc(int sz) { 52 | void* ret = malloc(sz); 53 | if (ret == NULL) assert(0); 54 | //printf("_Alloc(%d) -> %p\n", sz, ret); 55 | return ret; 56 | } 57 | 58 | int _ReadInteger() { 59 | int x; 60 | scanf("%d", &x); 61 | return x; 62 | } 63 | 64 | void _Halt() { 65 | exit(1); 66 | } 67 | 68 | int _StringEqual(const char *a, const char *b) { 69 | return (!strcmp(a, b)); 70 | } 71 | 72 | #define READ_LINE_LEN 1024 73 | const char* _ReadLine(void) { 74 | // delibrate memory leak 75 | char *a = malloc(READ_LINE_LEN); 76 | assert(a != NULL); 77 | return fgets(a, READ_LINE_LEN, stdin); 78 | } 79 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'decaf' 2 | -------------------------------------------------------------------------------- /src/main/java/decaf/Main.java: -------------------------------------------------------------------------------- 1 | package decaf; 2 | 3 | import decaf.driver.Launcher; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | Launcher.withArgs(args); 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/java/decaf/backend/asm/Asm.java: -------------------------------------------------------------------------------- 1 | package decaf.backend.asm; 2 | 3 | import decaf.backend.dataflow.CFGBuilder; 4 | import decaf.backend.dataflow.LivenessAnalyzer; 5 | import decaf.backend.reg.RegAlloc; 6 | import decaf.driver.Config; 7 | import decaf.driver.Phase; 8 | import decaf.lowlevel.log.Log; 9 | import decaf.lowlevel.tac.TacProg; 10 | import decaf.printing.PrettyCFG; 11 | 12 | import java.io.FileNotFoundException; 13 | import java.io.PrintWriter; 14 | import java.util.logging.Level; 15 | 16 | /** 17 | * The assembly code generation phase: translate a TAC program to assembly code. 18 | */ 19 | public class Asm extends Phase { 20 | /** 21 | * Helper assembly code emitter. 22 | */ 23 | protected final AsmEmitter emitter; 24 | 25 | /** 26 | * Register allocator. 27 | */ 28 | protected final RegAlloc regAlloc; 29 | 30 | public Asm(AsmEmitter emitter, RegAlloc regAlloc, Config config) { 31 | super("asm: " + emitter.toString(), config); 32 | this.regAlloc = regAlloc; 33 | this.emitter = emitter; 34 | } 35 | 36 | @Override 37 | public String transform(TacProg prog) { 38 | Log.info("phase: asm"); 39 | 40 | var analyzer = new LivenessAnalyzer<>(); 41 | 42 | for (var vtbl : prog.vtables) { 43 | Log.info("emit vtable for %s", vtbl.className); 44 | emitter.emitVTable(vtbl); 45 | } 46 | 47 | emitter.emitSubroutineBegin(); 48 | for (var func : prog.funcs) { 49 | Log.info("emit func for %s", func.entry.prettyString()); 50 | var pair = emitter.selectInstr(func); 51 | for (var x : pair.getLeft()) { 52 | Log.info("%s", x.toString()); 53 | } 54 | var builder = new CFGBuilder<>(); 55 | var cfg = builder.buildFrom(pair.getLeft()); 56 | analyzer.accept(cfg); 57 | Log.ifLoggable(Level.FINE, printer -> new PrettyCFG<>(printer).pretty(cfg)); 58 | regAlloc.accept(cfg, pair.getRight()); 59 | } 60 | 61 | return emitter.emitEnd(); 62 | } 63 | 64 | @Override 65 | public void onSucceed(String code) { 66 | if (config.target.equals(Config.Target.PA5_X86) || config.target.equals(Config.Target.PA5_MIPS)) { 67 | var path = config.dstPath.resolve(config.getSourceBaseName() + ".s"); 68 | try { 69 | var printer = new PrintWriter(path.toFile()); 70 | printer.print(code); 71 | printer.close(); 72 | } catch (FileNotFoundException e) { 73 | e.printStackTrace(); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/decaf/backend/asm/AsmEmitter.java: -------------------------------------------------------------------------------- 1 | package decaf.backend.asm; 2 | 3 | import decaf.lowlevel.AsmCodePrinter; 4 | import decaf.lowlevel.instr.PseudoInstr; 5 | import decaf.lowlevel.instr.Reg; 6 | import decaf.lowlevel.tac.TacFunc; 7 | import decaf.lowlevel.tac.VTable; 8 | import org.apache.commons.lang3.tuple.Pair; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Emit assembly code. 14 | */ 15 | public abstract class AsmEmitter { 16 | 17 | /** 18 | * Target platform name. 19 | */ 20 | public final String platformName; 21 | 22 | /** 23 | * Platform-specific registers accounted for register allocation, i.e. general registers. 24 | */ 25 | public final Reg[] allocatableRegs; 26 | 27 | /** 28 | * Platform-specific registers that need be saved by caller. 29 | */ 30 | public final Reg[] callerSaveRegs; 31 | 32 | public AsmEmitter(String platformName, Reg[] allocatableRegs, Reg[] callerSaveRegs) { 33 | this.platformName = platformName; 34 | this.allocatableRegs = allocatableRegs; 35 | this.callerSaveRegs = callerSaveRegs; 36 | } 37 | 38 | /** 39 | * Emit assembly code for a virtual table. 40 | * 41 | * @param vtbl virtual table 42 | */ 43 | public abstract void emitVTable(VTable vtbl); 44 | 45 | /** 46 | * Instruction selection for a TAC function. 47 | *

48 | * Since no register allocation is done, the generated instructions may still contain pseudo registers (temps). 49 | * 50 | * @param func TAC function 51 | * @return a pair of the instruction sequence, and the basic info of the function 52 | */ 53 | public abstract Pair, SubroutineInfo> selectInstr(TacFunc func); 54 | 55 | /** 56 | * Call this when all virtual tables are done, and you want to emit code for subroutines. 57 | */ 58 | public abstract void emitSubroutineBegin(); 59 | 60 | /** 61 | * Begin to emit code for a subroutine. 62 | * 63 | * @param info basic info of this subroutine 64 | * @return emitter of this subroutine 65 | */ 66 | public abstract SubroutineEmitter emitSubroutine(SubroutineInfo info); 67 | 68 | /** 69 | * Call this when all subroutines are done, and you want to finish. 70 | * 71 | * @return string representation of the emitted assembly code 72 | */ 73 | public abstract String emitEnd(); 74 | 75 | /** 76 | * Assembly code pretty printer. 77 | */ 78 | protected final AsmCodePrinter printer = new AsmCodePrinter(); 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/decaf/backend/asm/SubroutineEmitter.java: -------------------------------------------------------------------------------- 1 | package decaf.backend.asm; 2 | 3 | import decaf.lowlevel.AsmCodePrinter; 4 | import decaf.lowlevel.instr.HoleInstr; 5 | import decaf.lowlevel.instr.NativeInstr; 6 | import decaf.lowlevel.instr.Reg; 7 | import decaf.lowlevel.instr.Temp; 8 | import decaf.lowlevel.label.Label; 9 | 10 | /** 11 | * Emit assembly code for a subroutine. 12 | */ 13 | public abstract class SubroutineEmitter { 14 | 15 | protected SubroutineEmitter(AsmEmitter emitter, SubroutineInfo info) { 16 | this.info = info; 17 | this.printer = emitter.printer; 18 | } 19 | 20 | /** 21 | * Append an assembly comment. 22 | * 23 | * @param comment does not include the leading comment indicator 24 | */ 25 | public abstract void emitComment(String comment); 26 | 27 | /** 28 | * Append an assembly instruction that stores the value of a register to stack. 29 | * 30 | * @param src source register 31 | */ 32 | public abstract void emitStoreToStack(Reg src); 33 | 34 | /** 35 | * Append an assembly instruction that loads a value from stack to a register. 36 | * 37 | * @param dst destination register 38 | * @param src source temp 39 | */ 40 | public abstract void emitLoadFromStack(Reg dst, Temp src); 41 | 42 | /** 43 | * Append an assembly instruction that copies value between two registers. 44 | * 45 | * @param dst destination register 46 | * @param src source register 47 | */ 48 | public abstract void emitMove(Reg dst, Reg src); 49 | 50 | /** 51 | * Append a given assembly instruction. 52 | * 53 | * @param instr assembly instruction 54 | */ 55 | public abstract void emitNative(NativeInstr instr); 56 | 57 | /** 58 | * Append a label. 59 | * 60 | * @param label label 61 | */ 62 | public abstract void emitLabel(Label label); 63 | 64 | /** 65 | * Call this when you have appended all user and synthetic (by register allocation algorithm) instructions of 66 | * this subroutine. 67 | */ 68 | public abstract void emitEnd(); 69 | 70 | /** 71 | * Basic info of this subroutine. 72 | */ 73 | protected SubroutineInfo info; 74 | 75 | /** 76 | * Assembly code pretty printer. 77 | */ 78 | protected AsmCodePrinter printer; 79 | 80 | /** 81 | * Expand hole instructions with necessary native instructions. 82 | * 83 | * Note that hole instruction expansion is done after register allocation. 84 | */ 85 | public void emitHoleInstr(HoleInstr instr, Reg[] srcRegs, Reg[] dstRegs) {} 86 | } -------------------------------------------------------------------------------- /src/main/java/decaf/backend/asm/SubroutineInfo.java: -------------------------------------------------------------------------------- 1 | package decaf.backend.asm; 2 | 3 | import decaf.lowlevel.label.FuncLabel; 4 | 5 | /** 6 | * Basic info of subroutine. 7 | */ 8 | public class SubroutineInfo { 9 | /** 10 | * Label of the function entry. 11 | */ 12 | public final FuncLabel funcLabel; 13 | 14 | /** 15 | * Number of arguments. 16 | */ 17 | public final int numArg; 18 | 19 | /** 20 | * Does this subroutine call others? 21 | */ 22 | public final boolean hasCalls; 23 | 24 | /** 25 | * Max. stack size needed to store arguments. 26 | */ 27 | public final int argsSize; 28 | 29 | public SubroutineInfo(FuncLabel funcLabel, int numArg, boolean hasCalls, int argsSize) { 30 | this.funcLabel = funcLabel; 31 | this.numArg = numArg; 32 | this.hasCalls = hasCalls; 33 | this.argsSize = argsSize; 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/decaf/backend/asm/mips/MipsSubroutineEmitter.java: -------------------------------------------------------------------------------- 1 | package decaf.backend.asm.mips; 2 | 3 | import decaf.backend.asm.SubroutineEmitter; 4 | import decaf.backend.asm.SubroutineInfo; 5 | import decaf.lowlevel.Mips; 6 | import decaf.lowlevel.instr.HoleInstr; 7 | import decaf.lowlevel.instr.NativeInstr; 8 | import decaf.lowlevel.instr.Reg; 9 | import decaf.lowlevel.instr.Temp; 10 | import decaf.lowlevel.label.Label; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.TreeMap; 16 | 17 | /** 18 | * Emit MIPS assembly code for a subroutine. 19 | *

20 | * Recall the stack frame of a MIPS subroutine looks this: 21 | *

 22 |  *                  previous stack frame ...
 23 |  * SP + 4n + 40 + : local data m - 1
 24 |  * 4(m - 1)
 25 |  *               ...
 26 |  * SP + 4n + 40   : local data 0
 27 |  * SP + 4n + 36   : ($RA)
 28 |  * SP + 4n + 32   : ($S8)
 29 |  *               ...
 30 |  * SP + 4n + 0    : ($S0)
 31 |  * SP + 4(n - 1)  : arg n - 1
 32 |  *               ...
 33 |  * SP + 16        : arg 4
 34 |  *               ...
 35 |  * SP             : (arg 0)
 36 |  * 
37 | *

38 | * The parenthesized slots may not be used, but to make our life easier, we always reserve them. 39 | */ 40 | public class MipsSubroutineEmitter extends SubroutineEmitter { 41 | 42 | MipsSubroutineEmitter(MipsAsmEmitter emitter, SubroutineInfo info) { 43 | super(emitter, info); 44 | nextLocalOffset = info.argsSize + 4 * Mips.calleeSaved.length + 4; 45 | printer.printLabel(info.funcLabel, "function " + info.funcLabel.prettyString()); 46 | } 47 | 48 | @Override 49 | public void emitComment(String comment) { 50 | buf.add(NativeInstr.nativeComment(String.format("; %s", comment))); 51 | } 52 | 53 | @Override 54 | public void emitStoreToStack(Reg src) { 55 | if (!offsets.containsKey(src.temp)) { 56 | if (src.temp.index < info.numArg) { // Always map arg `i` to `SP + 4 * i`. 57 | offsets.put(src.temp, 4 * src.temp.index); 58 | } else { 59 | offsets.put(src.temp, nextLocalOffset); 60 | nextLocalOffset += 4; 61 | } 62 | } 63 | 64 | buf.add(new Mips.NativeStoreWord(src, Mips.SP, offsets.get(src.temp))); 65 | } 66 | 67 | @Override 68 | public void emitLoadFromStack(Reg dst, Temp src) { 69 | if (!offsets.containsKey(src)) { 70 | if (src.index < info.numArg) { // arg 71 | var offset = 4 * src.index; 72 | offsets.put(src, offset); 73 | buf.add(new Mips.NativeLoadWord(dst, Mips.SP, offset)); 74 | return; 75 | } 76 | 77 | throw new IllegalArgumentException("offsets doesn't contain " + src + " when loading " + dst); 78 | } 79 | 80 | buf.add(new Mips.NativeLoadWord(dst, Mips.SP, offsets.get(src))); 81 | } 82 | 83 | @Override 84 | public void emitMove(Reg dst, Reg src) { 85 | buf.add(new Mips.NativeMove(dst, src)); 86 | } 87 | 88 | @Override 89 | public void emitNative(NativeInstr instr) { 90 | buf.add(instr); 91 | } 92 | 93 | @Override 94 | public void emitLabel(Label label) { 95 | buf.add(new Mips.MipsLabel(label).toNative(new Reg[]{}, new Reg[]{})); 96 | } 97 | 98 | @Override 99 | public void emitEnd() { 100 | printer.printComment("start of prologue"); 101 | printer.printInstr(new Mips.SPAdd(-nextLocalOffset), "push stack frame"); 102 | if (Mips.RA.isUsed() || info.hasCalls) { 103 | printer.printInstr(new Mips.NativeStoreWord(Mips.RA, Mips.SP, info.argsSize + 4 * Mips.calleeSaved.length), 104 | "save the return address"); 105 | } 106 | for (var i = 0; i < Mips.calleeSaved.length; i++) { 107 | if (Mips.calleeSaved[i].isUsed()) { 108 | printer.printInstr(new Mips.NativeStoreWord(Mips.calleeSaved[i], Mips.SP, info.argsSize + 4 * i), 109 | "save value of $S" + i); 110 | } 111 | } 112 | printer.printComment("end of prologue"); 113 | printer.println(); 114 | 115 | printer.printComment("start of body"); 116 | for (var i = 0; i < Math.min(info.numArg, Mips.argRegs.length); i++) { 117 | printer.printInstr(new Mips.NativeStoreWord(Mips.argRegs[i], Mips.SP, 4 * i), 118 | "save arg " + i); 119 | } 120 | for (var i = Mips.argRegs.length; i < info.numArg; i++) { 121 | printer.printInstr(new Mips.NativeLoadWord(Mips.callerSaved[0], Mips.SP, nextLocalOffset + 4 * i), 122 | "load arg " + i); 123 | printer.printInstr(new Mips.NativeStoreWord(Mips.callerSaved[0], Mips.SP, 4 * i), 124 | "save arg " + i); 125 | } 126 | for (var instr : buf) { 127 | printer.printInstr(instr); 128 | } 129 | printer.printComment("end of body"); 130 | printer.println(); 131 | 132 | printer.printLabel(new Label(info.funcLabel.name + Mips.EPILOGUE_SUFFIX)); 133 | printer.printComment("start of epilogue"); 134 | for (var i = 0; i < Mips.calleeSaved.length; i++) { 135 | if (Mips.calleeSaved[i].isUsed()) { 136 | printer.printInstr(new Mips.NativeLoadWord(Mips.calleeSaved[i], Mips.SP, info.argsSize + 4 * i), 137 | "restore value of $S" + i); 138 | } 139 | } 140 | if (Mips.RA.isUsed() || info.hasCalls) { 141 | printer.printInstr(new Mips.NativeLoadWord(Mips.RA, Mips.SP, info.argsSize + 4 * Mips.calleeSaved.length), 142 | "restore the return address"); 143 | } 144 | printer.printInstr(new Mips.SPAdd(nextLocalOffset), "pop stack frame"); 145 | printer.printComment("end of epilogue"); 146 | printer.println(); 147 | 148 | printer.printInstr(new Mips.NativeReturn(), "return"); 149 | printer.println(); 150 | } 151 | 152 | private List buf = new ArrayList<>(); 153 | 154 | private int nextLocalOffset; 155 | 156 | private Map offsets = new TreeMap<>(); 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/decaf/backend/dataflow/BasicBlock.java: -------------------------------------------------------------------------------- 1 | package decaf.backend.dataflow; 2 | 3 | import decaf.lowlevel.instr.PseudoInstr; 4 | import decaf.lowlevel.instr.Temp; 5 | import decaf.lowlevel.label.Label; 6 | 7 | import java.util.*; 8 | 9 | /** 10 | * A basic block in a control flow graph. 11 | * 12 | * @param type of the instructions stored in the block 13 | */ 14 | public class BasicBlock implements Iterable> { 15 | /** 16 | * Kind of the block. 17 | *

    18 | *
  1. {@code CONTINUOUS}: continue executing the next block after this one
  2. 19 | *
  3. {@code END_BY_JUMP}: this block ends by a jump instruction
  4. 20 | *
  5. {@code END_BY_COND_JUMP}: this block ends by a conditional jump instruction
  6. 21 | *
  7. {@code END_BY_RETURN}: this block ends by a return instruction
  8. 22 | *
23 | */ 24 | public enum Kind { 25 | CONTINUOUS, END_BY_JUMP, END_BY_COND_JUMP, END_BY_RETURN 26 | } 27 | 28 | public final Kind kind; 29 | 30 | /** 31 | * Block id. 32 | */ 33 | public final int id; 34 | 35 | /** 36 | * Entry label of this block, if any. 37 | */ 38 | public final Optional