Try   HackMD

μRust: A Simple Rust Programming Language

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.

  • Environmental Setup
    • OS: Ubuntu 20.04 LTS
    • Install dependencies: $ sudo apt install flex bison
    • Java Virtual Machine (JVM): $ sudo apt install default-jre
    • Java Assembler (Jasmin) is included in the Compiler hw3 le.
    • Judgmental tool: $ pip3 install local-judge

Java Assembly Code Generation

In this assignment, you have to build a μRust compiler. The descriptions for the execution steps are as follows.

  1. Build your μRust compiler by injecting the Java assembly code into your flex/bison code developed in the previous assignments.
  2. Run the compiler with the given μRust program (e.g., test.rs le) to generate the corresponding Java assembly code (e.g., test.j le).
  3. Run the Java assembler, Jasmin, to convert the Java assembly code into the Java bytecode (e.g., test.class le).
  4. Run the generated Java bytecode (e.g., test.class le) with JVM and display the results.

Workflow Of The Assignment

You are required to build a μRust compiler based on the previous two assignments. The execution steps are described as follows.

  1. Build your compiler by make command and you will get an executable named mycompiler .
  2. Run your compiler using the command $ ./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).
  3. The Java assembly code can be converted into the Java Bytecode ( .class le) through the Java assembler, Jasmin, i.e., use $ java -jar jasmin.jar hw3.j to generate Main.class .
  4. Run the Java program ( .class le) with Java Virtual Machine (JVM); the program should generate the execution results required by this assignment, i.e., use $ java Main to run the executable.

What Should Your Compiler Do?

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.

Submission

We use GitHub Classroom to collect assignments from students. Push your code to Github before the deadline.

Demonstration of Your Assignment 3

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.

Reference

Appendix (Jasmin Instructions)

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.

Setup Code

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

Literals

Constant in μRust Jasmin Instruction
25 ldc 25
3.14 ldc 3.14
"Hi" ldc "Hi"
true / false iconst_1 / iconst_0

Print

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.

  • μRust Code
println(30)
print("Hello")
  • Jasmin Code (for reference only)
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

Operations

Unary Operators

μRust Operator Jasmin Instruction(i32) Jasmin Instruction(f32)
+ (ignore) (ignore)
- ineg fneg

Binary Operators

μRust Operator Jasmin Instruction(i32) Jasmin Instruction(f32)
+ iadd fadd
- isub fsub
* imul fmul
/ idiv fdiv
% irem -

Bitwise Operators

μ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.

  • μRust Code
-5 + 3 * 2;
  • Jasmin Code (for reference only)
ldc 5
ineg
ldc 3
ldc 2
imul
iadd

Store/Load Variables

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.

  • μRust Code
x = 9;
y = 4 + x;
z = "Hello";

  • Jasmin Code (for reference only)
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

Jump Instruction

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
  • μRust Code
if x == 10 {
/* do something */
} else {
/* do the other thing */
}

  • Jasmin Code (for reference only)
    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:

Type Conversions

The following example shows the usage of the casting instructions, i2f and f2i , where x is i32 local variable 0.

  • μRust Code
x = x + 6.28 as i32;
  • Jasmin Code (for reference only)
iload 0
ldc 6.28
f2i
iadd
istore 0

Method Invocation

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.

  • μRust Code
fn foo(x: i32, y: i32) ->i32 {
    return x + y;
}
fn main() {
    let z: i32 = foo(3, 4);
    println(z);
}

  • Jasmin Code (for reference only)
.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