This tutorial covers only a subset of JVM specification, which is enough for a person who will use JVM as a target language for C+-.
By virtue of being a virtual machine, JVM, can as well be an interface between another programming language and the hardware, which will also make that language portable. So, 'C+- is a portable programming language' is perfectly a true statement, for anyone who is saying 'Java is a portable programming language'.
Like all other virtual machines, JVM has an instruction set. There are nearly 255 instructions in this set, however you will not be dealing with all of them.
JVM takes "class" files
as input. Class files are binary files, so it is not an easy job to write
a class file from scratch. So what you need is a tool to convert an ASCII
JVM code to a binary class file. Here's where Jasmin comes into picture.
You can think of Jasmin as a Java Assembler. So, in the third phase of
your project, you will be producing Jasmin files instead of binary "class"
files, which will ease your job a lot.
A simple program in C+- :
int main()
{
int x;
x=input();
x=x+3;
output(x);
return 0;
}
The same program in Jasmin :
.class public simple
.super java/lang/Object
.method public <init>()V
aload_0
invokespecial java/lang/Object/<init>()V
return
.end method
.method public static
main([Ljava/lang/String;)V
.limit
stack 5
.limit
locals 100
ldc
0
istore
1 ; initialize x to zero and store it in
local variable 1
; the input function starts at this point
ldc
0
istore
50 ; storage for a dummy integer for reading it
by input()
Label1:
getstatic
java/lang/System/in Ljava/io/InputStream;
invokevirtual
java/io/InputStream/read()I
istore
51
iload
51
ldc
10
isub
ifeq
Label2
iload
51
ldc
32
isub
ifeq
Label2
iload
51
ldc
48
isub
ldc
10
iload
50
imul
iadd
istore
50
goto
Label1
Label2:
; now our dummy integer contains the integer read from the keyboard
iload
50 ; input function ends here
istore
1 ; store this value in x
iload
1
ldc
3
iadd
istore
1 ; x=x+3
iload
1
getstatic
java/lang/System/out Ljava/io/PrintStream;
swap
invokevirtual
java/io/PrintStream/println(I)V ;output(x)
return
; return from main
The Jasmin code for the program looks quite complicated compared to the C+- version, but you do not need to know all the things around. Most of the code you see above will be present in all of the files you generate. So you can consider the above Jasmin code as a template.
A Jasmin file consists of directives, labels and instructions. The lines beginning with a "." (dot) are directives, labels are names followed by a ":", and a newline. The rest are considered as instructions.
.class directive tells Jasmin the name of the class being defined.
.super directive tells Jasmin the class which our class is extending. It will be java/lang/Object for all C+- programs.
The method (function) definitions begin with a .method directive and end with .end method directive.
The <init> method is an instance initialization method and again will be present for all C+- programs. It is used to initialize a new instance of the class. This is a process, which also includes initialization of the inherited class, which, in this case, is java/lang/Object.
The main method is where the translated code of your C+- program resides. Notice that the main function has a parameter which is an array of strings and it returns void. The main function for JVM has to have these specifications to qualify as an entry point to the program.
.limit stack 5 directive in main method sets the size of the operand stack for method "main" to 5. This means, we can push up to five items on that method's operand stack during the execution of the method. Each method has its own operand stack in JVM.
.limit locals 100 directive sets the number of local variables in method "main" to 100. It is set to 1 by default. Use this directive in a method which has more than one local variable.
The rest in the above
code are the instructions, which are explained in detail in 'The Instruction
Set' section.
F single-precision IEEE 754 float
I integer
L<classname>; an instance of the class
[ one array dimension
V void for return type
example :
int f(int a, string b,float c)
will have a descriptor,
.method public static
f(ILjava/lang/String;F)I
Now, take a look at the simple program's main method :
.method public static
main([Ljava/lang/String;)V
It is a method which
takes an array of strings as a parameter and returns no result. Notice
that string is not a primitive type in JVM, so we have to use the Ljava/lang/String
descriptor for strings, which represents the String object of Java.
mnemonic { parameters }
brief description
(stack before => stack after)
Here are the instructions you may need to use :
Load and Store Instructions :
iload <local_variable_number>
pushes the value of the local variable which is an integer.
(.... => .....value)
istore <local_variable_number>
pops the value which is an integer and stores it in local variable.
(.....value => ......)
fload <local_variable_number>
pushes the value of the local variable which is a float.
(..... => ......value)
fstore <local_variable_number>
pops the value which is a float and stores it in local variable.
(.....value => ......)
ldc <constant>
push constant on the stack.
(....... => .......constant)
Stack Manipulation Instructions :
dup
the word on top of the stack is duplicated.
(......word => ......word,word)
pop
pop top word off the operand stack.
(.....word => .......)
swap
swap two operand stack words.
(.......word1, word2 => ........word2, word1)
Arithmetic Instructions :
iadd
add int.
(.......value1, value2 => ...........value1+value2)
idiv
divide int
(.......value1, value2 => ..........value1/value2)
imul
multiply int
(.........value1, value2 => ..........value1*value2)
isub
subtract int
(.......value1, value2 => ..........value1-value2)
fadd
add float
(........value1, value2 => ........value1+value2)
fdiv
divide float
(.........value1, value2 => .......value1/value2)
fmul
multiply float
(........value1, value2 => .......value1*value2)
fsub
subtract float
(........value1, value2 => ........value1-value2)
Branch Instructions :
goto <label>
jump to label
(no change in stack)
ifeq <label>
jump to label if the value on top of stack is 0
(.......value => ........)
ifge <label>
jump to label if the value on top of the stack is >=0
(.........value => .........)
ifgt <label>
jump to label if the value on top of the stack is >0
(........value => ..........)
ifle <label>
jump to label if the value on top of the stack is <=0
(........value => .........)
iflt <label>
jump to label if the value on top of the stack is <0
(........value => ........)
ifne <label>
jump to label if the value on top of the stack is not equal to 0
(........value => ..........)
Logical Instructions :
iand
value1 and value2, which must be of type int, are popped from the operand stack and an int result is calculated by taking the bitwise AND (conjunction) of value1 and value2, then the result is pushed onto the operand stack.(..........value1,value2 => .........result)
ior
value1 and value2, which must be of type int, are popped from the operand stack and an int result is calculated by taking the bitwise OR (disjunction) of value1 and value2, then the result is pushed onto the operand stack.(..........value1,value2 => ........result)
Conversion Instructions :
i2f
convert int to float.
(.........int_value => ........float_value)
f2i
convert float to int.
(........float_value => .......int_value)
Subroutine Instructions:
jsr <label>
Pushes the return address on the stack and jumps to subroutine indicated by the label.
( ...... => ......returnAddress)
ret <local_variable_number>
Returns from subroutine to the return address which is stored in a local variable.
(no change)
To implement a function in C+- as a subroutine, what you have to do is to push the arguments in the caller statement and jump to the beginning of the function by using 'jsr'. In the beginning of the function you have to pop the return address and store it in a local variable, and pop the argument, store them in local variables. For return you have to push the return value and use 'ret' instruction to return to the execution point just after the 'jsr' instruction. With 'ret', you have to us the local variable number that you used to store the return address.
5. Standard Library Functions of C+-
int input()
.method public static
input()I
.limit locals 10
.limit stack 10
ldc 0
istore 1 ; this will hold our final integer
Label1:
getstatic java/lang/System/in Ljava/io/InputStream;
invokevirtual java/io/InputStream/read()I
istore 2
iload 2
ldc 10 ; the newline delimiter
isub
ifeq Label2
iload 2
ldc 32 ; the space delimiter
isub
ifeq Label2
iload 2
ldc 48 ; we have our digit in ASCII, have to subtract it from
48
isub
ldc 10
iload 1
imul
iadd
istore 1
goto Label1
Label2:
;when we come here we have our integer computed in Local Variable 1
iload 1
ireturn
.end method
int output(int)
.method public static
output(I)I
.limit locals 5
.limit stack 5
iload 0
getstatic java/lang/System/out Ljava/io/PrintStream;
swap
invokevirtual java/io/PrintStream/println(I)V
ldc 0
ireturn
.end method
In this section a C+- program which has a recursive function is translated into Jasmin.
This example will give you an idea on handling function calls, especially recursive ones. Here is our C+- code for the example:int main()
{
int x;
int y;int gcd (int u, int v)
{
int tmp;if (u < v)
{
tmp=u;
u=v;
v=tmp;
}
if (v == 0) return u ;
else return gcd(v,u-v);
}/* statements of main */
x = input();
y = input();
output(gcd(x,y));return 0;
}Here is the Jasmin code for the above C+- program:
.class public gcd
.super java/lang/Object.method public <init>()V
aload_0
invokespecial java/lang/Object/<init>()V
return
.end method
.method public static input()I
.limit locals 10
.limit stack 10ldc 0
istore 1 ; this will hold our final integer
Label1:
getstatic java/lang/System/in Ljava/io/InputStream;
invokevirtual java/io/InputStream/read()I
istore 2
iload 2
ldc 10 ; the newline delimiter
isub
ifeq Label2
iload 2
ldc 32 ; the space delimiter
isub
ifeq Label2iload 2
ldc 48 ; we have our digit in ASCII, have to subtract it from 48
isub
ldc 10
iload 1
imul
iadd
istore 1
goto Label1
Label2:
;when we come here we have our integer computed in Local Variable 1
iload 1
ireturn
.end method
.method public static output(I)I
.limit locals 5
.limit stack 5iload 0 ; the argument to function
getstatic java/lang/System/out Ljava/io/PrintStream;
swap
invokevirtual java/io/PrintStream/println(I)V
ldc 0
ireturn
.end method
.method public static main([Ljava/lang/String;)V
.limit stack 150
.limit locals 150ldc 0
istore 0 ; initialize x
ldc 0
istore 1 ; initialize y
goto LmainLgcd:
istore 10 ; store return address in local variable 10
istore 12 ; store y in local variable 12 (v in this scope)
istore 11 ; store x in local variable 11 (u in this scope)ldc 0
istore 13 ; initialize tmpiload 11
iload 12
isub ; u-v
iflt label1
goto label2
label1:
iload 11
istore 13 ; tmp=u
iload 12
istore 11 ; u=v
iload 13
istore 12 ; v=tmp
label2:
ldc 0
iload 12
isub
ifeq label3
goto label4
label3:
iload 11
ret 10 ; return from gcd call
label4:
iload 10
iload 11
iload 12
iload 13 ; push all locals on stack before the recursive calliload 12 ; push argument v
iload 11
iload 12
isub ; push argument u-vjsr Lgcd ; the recursive call
swap ; this is for restoring the locals we pushed before jsr
istore 13 ; the stack is (....locals,returned_result)
swap ; this "swap" solution works since our recursive
istore 12 ; function has one return value
swap ; as all other C+- functions.
istore 11
swap
istore 10
; now we have restored our locals
; the stack is (....returned_result)ret 10 ; we return the returned_result of the recursive call
Lmain:
invokestatic io/input()I
istore 0 ; x=input()
invokestatic io/input()I
istore 1 ; y=input()
iload 0
iload 1 ; note : y is on top of x
jsr Lgcd
invokestatic io/output(I)I ; we have the result of gcd on the stackreturn
.end method
7. How to Run Jasmin
>jasmin myprog.j
Generated myprog.class
>java myprog