Java GrinderJava, Java everywhere and all the code did shrink... Posted: January 05, 2014 Introduction Java Grinder is a tool that gives the ability to write programs in Java to run natively on microcontrollers, game consoles, and computers. Java Grinder is not a JVM, but instead translates byte-code from Java .class files into native assembly code much like a JIT or an "ahead of time" compiler. For the microcontrollers, there is a Java API to take advantage of the I/O ports, UART, SPI, and more. On the game console / computer side, there are Java API's to manipulate the graphics and sound chips. Currently, the following CPUs / platforms are supported (click on links for videos):
An interesting mention, Theodoulos Liontakis has created his own CPU / computer called the Lion Computer and added Java Grinder support for it. The entire Java Grinder code-base was implemented in a modular object-oriented way to make easy to add support for different CPUs. I started the project with support for MSP430 and soon after Joe Davisson picked it up by adding code for 6502, Atmel AVR8, and W65C816, along with APIs for Commodore 64, Apple IIgs, and W65C265SXB. Amstrad CPC support was later added by Carsten Dost. Lots of other architectures followed. Other future plans are to possibly add other byte-code parsers such as .NET so Java Grinder can also compile C# programs into native code, but we'll see if that ever happens. For a more complete description of how Java Grinder works, read the last section of this page. All generated assembly can be assembled with naken_asm except the x86 code which is assembled with The Netwide Assembler (nasm). Java Grinder can do bits of optimization to improve the speed of the generated code. For example, despite Java being a stack based language, for most of the implemented CPUs, Java Grinder keeps values in registers. It can also do 1 or 2 instruction look-aheads to find better instructions for certain CPU's. An example of this is explained below. Also, the API is written so that every method is static and when the assembly language is generated, many API methods are replaced with inline assembly instead of function calls. There are currently some restrictions with how this works:
These features could be added in the future. Repository Download Related Projects @mikekohn.net Examples Joe Davisson did the 6502 code along with a demo and two games for the Commodore 64 along with a video game for the Atari 2600. He has an explanation along with videos (and more Java C64 games) on his webpage. Below is a vdieo of the C64 demo and the Space Revenge Atari 2600 game written in Java: YouTube Link: https://youtu.be/f85XkyO0EdA YouTube Link: https://youtu.be/VN-8Z4VUjzg Below are demos of Java programs running on a Sega Genesis and Playstation 2. More info on the Sega Genesis Java and Playstation 2 Java pages. YouTube link: https://youtu.be/FdMh0TN6SlA YouTube link:https://youtu.be/AjM069oKUGs Below is a Parallax Propeller computing Mandelbrots in both assembly and in Java. A full explanation is on the mandelbrots_simd.php page. YouTube link: https://youtu.be/0DLUh5J-1Ho Below is another example showing and MSP430 and a Java Grinder program controlling an LCD display. Source code is again included in the samples directory of the repository. A couple other examples of Java Grinder running on MSP430 are the MSP430 VGA project and Cyborg Chicken project. YouTube link: https://youtu.be/q0PdsJ9YUkE Explanation In the git repository in the testing directory there is a program called LedBlink.java. To test it out to see how it works (naken_asm will need to be downloaded first), do the following:
git clone https://github.com/mikeakohn/naken_asm.git
cd naken_asm
./configure
make
cd ..
git clone https://github.com/mikeakohn/java_grinder.git
cd java_grinder
make
make java
cd samples/misc
make
cd ../..
./java_grinder samples/misc/LedBlink.class out.asm msp430g2231
../naken_asm/naken_asm -l -I ../naken_asm/include/msp430 out.asm
mspdebug rf2500
prog out.hex
run
The code in LedBlink.java looks like this:
static public void main(String args[])
{
int n;
IOPort0.setPinsAsOutput(0x3);
while(true)
{
IOPort0.setPortOutputValue(2);
for (n = 0; n < 16384; n++);
IOPort0.setPortOutputValue(1);
for (n = 0; n < 16384; n++);
}
}
Running javap -c testing/LedBlink.class, the Java assembly looks like this:
public static void main(java.lang.String[]);
Code:
0: iconst_3
// Method net/mikekohn/java_grinder/IOPort0.setPinsAsOutput:(I)V
1: invokestatic #2
4: iconst_2
// Method net/mikekohn/java_grinder/IOPort0.setPortOutputValue:(I)V
5: invokestatic #3
8: iconst_0
9: istore_1
10: iload_1
11: sipush 16384
14: if_icmpge 23
17: iinc 1, 1
20: goto 10
23: iconst_1
// Method net/mikekohn/java_grinder/IOPort0.setPortOutputValue:(I)V
24: invokestatic #3
27: iconst_0
28: istore_1
29: iload_1
30: sipush 16384
33: if_icmpge 4
36: iinc 1, 1
39: goto 29
And the code in out.asm looks like this:
main:
mov.w SP, r12 <-- need 2 variables: local #0 (function param)
sub.w #0x4, SP <-- and local #1 (n) so alloca() 4 bytes
bis.b #3, &P1DIR <-- inline method setPinsAsOutput(3)
main_4:
mov.b #2, &P1OUT <-- inline method setPortOutputValue(2)
mov.w #0, -4(r12) <-- set local variable #1 to 0
main_10:
mov.w -4(r12), r4
cmp.w #16738, r4 <-- check if local variable < 16738
jge main_23 <-- break if so
add.w #1, -4(r12) <-- add 1 to local variable #1
jmp main_10 <-- run for loop again
main_23:
mov.b #1, &P1OUT
mov.w #0, -4(r12)
main_29:
mov.w -4(r12), r4
cmp.w #16738, r4
jge main_4
add.w #1, -4(r12)
jmp main_29
When running the program looks like this: https://youtu.be/loHsatbB1dQ Inside Details The first step in developing Java Grinder was to take the source code for naken_java, change the formatting, and add a generator directory. I started out doing the generator by doing some object oriented C using function pointers, and then decided it would probably be better just doing this in C++, so I converted the whole thing to C++. In the generator directory there is an abstract super-class called Generator that contains all the functions that each architecture must implement (any unimplemented generator function returns -1 to let JavaCompiler.cxx know that this function isn't implemented). In the java directory there is a net/mikekohn/java_grinder directory that has the user visible JavaGrinder API. All classes/methods from this package are implemented in the Generator modules as functions with the name of the class in lowercase, underscore, and the method name unchanged. These functions are called by the functions in the objects/invoke.cxx file. The flow of the code basically looks like this:
Java is a stack-based assembly language, in other words if the code is a = 3 + 5, Java will push 3 on the stack, then push 5 on the stack, and then with an add instruction pop 3 and 5 off the stack, add them, and put the result back on the stack. To try and avoid using the stack on a microcontroller, which would probably be pretty slow, the generated code uses a "stack" of registers first (r4-r11 on MSP430) and when it runs out it will start using the real stack. This should help the speed of the generated code. Updates Update February 18, 2021: Added Intellivision / CP1610 support. Update December 23, 2018: I've spent a little time cleaning up some of the code in Java Grinder. The biggest change comes from, well.. since the JavaClass.cxx code came from a C project I was working on a few years back, all the strings were being done by fixed sized char arrays. This could backfire if a class, method, or field name is longer than the fixed size. I decided to change all those arrays to std::string for this reason, and also because if C++ hiring managers see the code they probably would have looked down on the C style char strings. Update August 18, 2018: Playstation 2 support is added to Java Grinder. Not sure what I'll add next, but I have some ideas in mind. Update January 23, 2018: Just a small update... I've been working on other projects so I haven't done much here in the past year or so. I did start adding more code for Playstation 2 support. I'm hoping in the next couple weeks I'll have a smallish demo working proving that it's possible to do what I'm wanting to do... which is drawing object using vector unit 1 to do rotations and placement in 3D space. Users of Java Grinder will simply have to load an object and draw it in an X,Y,Z location with 3 rotation values. Update March 25, 2017: I got a triangle to draw on the Playstation 2, so I'm putting that code into Java Grinder and going to continue to make an API around it. Update November 20, 2015: The Sega Genesis Java demo is done. Update August 1, 2015: I have Z80 program loading (with audio), pixel plotting, image displaying, and other stuff working on Sega Genesis. More to come. Update May 20, 2015: I have a simple Sega Genesis program written in Java writting in the MESS emulator. More to come... Update April 11, 2015: TMS9900 / TI99/4A is now working and I have a YouTube clip of Java running on TI99/4A on this Retro Console Java page. Update January 2, 2015: I've been kind of eyeballing the Intel Edison and Intel Galileo boards, so yesterday for fun I added x86 and x86_64 files to Java Grinder. I filled in about half of the x86 code. All that's missing is array stuff. Not sure how to do arrays. Should I use the C function malloc()? Or should I use the bss region to create some kind of heap? I put a little sample program in samples/x86 that assembles with The Netwide Assembler (nasm) and links to a C program so it runs on Linux from the command line. Update October 23, 2014: I have the TMS9900 code mostly written along with part of a TI99 API. Currently it's able to draw text on the screen and even render a Mandelbrot in text mode. I'm adding a higher resolution graphics mode with some plotting routines so a nicer demo can be made. I also need to finish with the array code in the TMS9900. Update April 6, 2014: I have Z80 code complete along with a little API for the TI84 Plus C calculator, but I haven't figured out how to get a program running in the Tilem emulator yet to make sure it works right. Hopefully soon. Update March 15, 2014: Joe has been working on optimizing the 6502 code and building the C64 package for his demo. I've also been asked a couple times about a C generator instead of assembly, which to me is kind of odd. Why not just write the code in C and not add another layer? The purpose of Java Grinder is to basically do what a JIT does inside of a JVM, except compile the code before being put onto the flash memory of a micro. A microcontroller wouldn't have enough memory to hold the JIT code anyway. Assembly text seemed like a good idea instead of straight byte code because it makes it easier to bug the assembled code and it takes advantage of naken_asm's features (elf output, label computing, etc). Either way I decided to drop it in quickly anyway just to see what the generated code looks like. It should be working properly except arrays.
Copyright 1997-2024 - Michael Kohn
|