Compiler 2023 Programming Assignment III
Compiler for Java Assembly Code Generation
Due Date: 2023/06/16
Lab3 link
This assignment is to generate Java assembly code (for Java Virtual Machines) of the given μRust program. The generated code will then be translated to the Java bytecode by the Java assembler, Jasmin. The generated Java bytecode should be run by the Java Virtual Machine (JVM) successfully.
$ sudo apt install flex bison
$ sudo apt install default-jre
$ pip3 install local-judge
In this assignment, you have to build a μRust compiler. The descriptions for the execution steps are as follows.
You are required to build a μRust compiler based on the previous two assignments. The execution steps are described as follows.
$ ./mycompiler < input.rs
, which is built by lex and yacc, with the given μRust code ( .rs le) to generate the corresponding Java assembly code ( .j le).$ java -jar jasmin.jar hw3.j
to generate Main.class .$ java Main
to run the executable.In Assignment 3, the flex/bison only need to print out the error messages, we score your assignment depending on the JVM execution result, i.e., the output of the command: $ java Main .
When ERROR occurs during the parsing phase, we expect your compiler to print out ALL error messages, as Assignment 2 did, and DO NOT generate the Java assembly code (.j le).
Each test case is 7pt, and you can check the correctness by local-judge (type judge command in your terminal) as hw1 and hw2.
We use GitHub Classroom to collect assignments from students. Push your code to Github before the deadline.
The form and schedule of demonstration will be announced on Moodle later. During the demonstration, you will be asked to demonstrate your assignment downloaded from Moodle and you need to answer the questions about the logics of your codes in 5 ~ 10 minutes. Note that TA will verify that your compiler is not hardcoded to the attached inputs and outputs. For the hardcoded case, you will get 0pt.
In this section, we list the Jasmin instructions that you may use in developing your compiler. Please note that the assignment are not limited to using the commands and approaches introduced below.
A valid Jasmin program should include the code segments for the execution environment setup. Your compiler should be able to generate the setup code, together with the translated Jasmin instructions (as shown in the previous paragraphs). The example code is listed as below.
.source hw3.j
.class public Main
.super java/lang/Object
; ... Your generated Jasmin code for the input μRust program ...
.method public static main([Ljava/lang/String;)V ; main function
.limit stack 100 ; Define your storage size.
.limit locals 100 ; Define your local space number.
; ... Your generated Jasmin code for the input μRust program ...
return
.end method
Constant in μRust | Jasmin Instruction |
---|---|
25 | ldc 25 |
3.14 | ldc 3.14 |
"Hi" | ldc "Hi" |
true / false | iconst_1 / iconst_0 |
The following example shows how to print out the constants with the Jasmin code. Note that there is a little bit dierent for the actual parameters of the println functions invoked by the invokevirtual instructions, i.e., i32 ( I ), f32 ( F ), and string ( Ljava/lang/String; ). Note also that you need to treat bool type as string when encountering print statement, and the corresponding code segments are shown as below.
println(30)
print("Hello")
ldc 30 ; integer
getstatic java/lang/System/out Ljava/io/PrintStream;
swap
invokevirtual java/io/PrintStream/println(I)V
ldc "Hello" ; string
getstatic java/lang/System/out Ljava/io/PrintStream;
swap
invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V
μRust Operator | Jasmin Instruction(i32) | Jasmin Instruction(f32) |
---|---|---|
+ | (ignore) | (ignore) |
- | ineg | fneg |
μRust Operator | Jasmin Instruction(i32) | Jasmin Instruction(f32) |
---|---|---|
+ | iadd | fadd |
- | isub | fsub |
* | imul | fmul |
/ | idiv | fdiv |
% | irem | - |
μRust Operator | Jasmin Instruction |
---|---|
& | iand |
| | ior |
^ | ixor |
<< | ishl |
>> | iushr |
The following example shows the standard unary and binary arithmetic operations in μRust and the corresponding Jasmin instructions.
-5 + 3 * 2;
ldc 5
ineg
ldc 3
ldc 2
imul
iadd
The following example shows how to load the constant at the top of the stack and store the value to the local variable ( x = 9; ). In addition, it then loads a constant to the Java stack, loads the content of the local variable, and adds the two values before the results are stored to the local variable ( y = 4 + x; ). Furthermore, the example code exhibits how to store a string to the local variable ( z = "Hello"; ). The contents of local variables after the execution of the Jasmin code are shown as below.
x = 9;
y = 4 + x;
z = "Hello";
ldc 9
istore 0 ; store 9 to x
ldc 4
iload 0 ; load x
iadd ; add 4 and x
istore 1 ; store the result to y
ldc "Hello"
astore 2 ; store a string to z
Jasmin Instruction | Description |
---|---|
goto <label> | direct jump |
ifeq <label> | jump if zero |
ifne <label> | jump if nonzero |
iflt <label> | jump if less than zero |
ifle <label> | jump if less than or equal to zero |
ifgt <label> | jump if greater than zero |
ifge <label> | jump if greater than or equal to zero |
if x == 10 {
/* do something */
} else {
/* do the other thing */
}
iload 0 ; load x
ldc 10 ; load integer 10
isub
ifeq L_cmp_0 ; jump to L_cmp_0 if x == 0
; if not, execute next line
iconst_0 ; false (if x != 0)
goto L_cmp_1 ; skip loading true to the stack
; by jumping to L_cmp_1
L_cmp_0: ; if x == 0 jump to here
iconst_1 ; true
L_cmp_1:
ifeq L_if_false
; -> do something
goto L_if_exit
L_if_false:
; -> do the other thing
L_if_exit:
The following example shows the usage of the casting instructions, i2f and f2i , where x is i32 local variable 0.
x = x + 6.28 as i32;
iload 0
ldc 6.28
f2i
iadd
istore 0
There are several forms of method-calling instructions in the JVM. In this homework, methods are called using the invokestatic instruction. The usage can be shown by following example. A function foo has signature (II)I that you have implemented in hw2, and this information is used during the code generation. The invokestatic Main/foo(II)I is used to invoke the method foo after two actual argumants ( 3 , 4 ) are loaded to the stack, and then the result of the function output will be pushed to the stack.
fn foo(x: i32, y: i32) ->i32 {
return x + y;
}
fn main() {
let z: i32 = foo(3, 4);
println(z);
}
.method public static foo(II)I ; Define foo function
.limit stack 20
.limit locals 20
iload 0 ; load the first argument
iload 1 ; load the second argument
iadd
ireturn
.end method
.method public static main([Ljava/lang/String;)V
.limit stack 100
.limit locals 100
ldc 3 ; push argument to the stack
ldc 4 ; push argument to the stack
invokestatic Main/foo(II)I ; invoke `foo` method in `Main` class
istore 2 ; store the result to z
iload 2 ; load z for println
getstatic java/lang/System/out Ljava/io/PrintStream;
swap
invokevirtual java/io/PrintStream/println(I)V
return
.end method