5

Please note: although I am using ARM SAM3X8E in this example, I'm just using that as a concrete example, and the answer to this question could easily be given using any other MCU such as AVR, etc.

I have a crazy question. In reality, its more about confirming my knowledge of low-level computer architecture than anything else, and is not necessarily something that I'm going to endeavour to actually do.

I am interested in understanding the process and tooling involved with writing a Java app on, say, a Linux or Windows machine, and then cross-compiling that app into an image that can be directly flashed to an MCU, such as the ARM SAM3X8E. I know, crazy.

Why would I ever want to do this, would be a sane person's first response. Again:

  • This question is more about confirming/clarifying my understanding of low-level programming/computer architecture than anything else; but
  • One possible reason to do this would be in a scenario where I have to have an MCU/ARM chip running an application (not a Linux box or anything else), but the application is so complicated that to write it in C and then maintain it would be a monumental undertaking. But, in Java, with its powerful syntax, data structure and massive ecosystem of 3rd party libraries/OSS components that can be leveraged, writing the app is many order of magnitude easier.

Even if you're still not convinced of my use case above (which, I'd be interested as to why), I am still interested in seeing how such a solution could be brute forced to work.

If I had to whip together a solution without any help/guidance, here's what I would do:

  • Implement my own JVM in C (lol)
  • Ultimately a Java app is fed into a JVM as input data (bytecode); and so the source code of this custom JVM would need to be hardcoded to look for a specific input file and read bytecode out of, we'll call this input file bytecode_input.c.
  • I would need some kind of a cross compiler that took Java bytecode (produced by javac) and converted it into bytecode_input.c. This way, when compiling my custom JVM, the bytecode of my app gets integrated into the resultant JVM executable.
  • Writing my custom JVM would also require implementation of certain system calls in C code, so that when an application uses FileWriter to write to a file, the JVM knows what system calls to make
  • Now I would just need to make sure that whatever is the output of the JVM's compilation is something that can be flashed to the MCU

Unless I'm missing anything (again, understanding that the steps above are by no means trivial and each of them are probably several man-years in the making), I believe that's all one would need. Basically, I write my app in Java, compile it to bytecode, run it through the bytecode_file_generator, turning it into a C file named bytecode_input.c. That C file then gets compiled with the rest of my JVM (which contains everything a standard JVM has, GC, memory management, bytecode verifier, classloader, etc.), which produces some executable/image that can be flashed to the MCU.

If bytecode_input.c seems mysterious and vague, here's some C pseudo-code for what I mean:

// This C file simply defines a variable called 'bytecode'
// which contains a static/hardcoded table of all the app's bytecode:
int[] BYTECODE = new int[SIZE_OF_CLASS_FILE] {
    103, 1929, 1939, 549, 9939,
    3949, 3949, 20304, 28, 238
};

The JVM then reads BYTECODE as hardcoded input instead of at runtime like a normal JVM.

Typically JVMs run on top of an OS. I don't have to worry about OSes because MCUs run programs bare metal, and if I have the need for RTOS-like functionality, I could just build that into the custom JVM.

I could (I suppose) just craft a very clever cross-compiler that converted my app's Java source into C, and skip involving a custom JVM altogether. But then I'd be losing out on all the good things the JVM provides: GC, memory management, etc.

So my question: assuming you can push the magical "Ok, I'll accept this as a valid use case for now..." button, is my approach here fundamentally wrong? Am I overlooking any major steps/processes?

smeeb
  • 4,820
  • 10
  • 30
  • 49
  • 3
    As an aside, Java (back when it was known as Oak) was originally intended to be used in an embedded environment. There have been attempts (some successful, none *popular* (to the best of my knowledge though I'll admit my rather poor knowledge in this domain)) at creating a [Java processor](http://en.wikipedia.org/wiki/Java_processor) where the Java byte code is the native or microcode interface to the processor. –  May 13 '15 at 18:19
  • Ahhh very interesting @MichaelT (+1) - that completely simplifies my entire thinking: a chip that *is* the JVM implementation itself! Two quick followups if you don't mind: (1) I feel like this is a perfect use case for ASIC, no? Then it's just a matter of "sending" app bytecode to the chip for processing....thoughts? And (2) Do you know of any hardware- or electronic-related reasons why such a Java processor/ASIC would be a terrible idea? Thanks again! – smeeb May 13 '15 at 18:25
  • 1: no idea. 2: no idea. (I had to go look up what ASIC was - I'm in the enterprise Java world and such things are more a curiosity for me than a practical concern or option). –  May 13 '15 at 18:45
  • 3
    Are you aware that there's is an edition of Java targeted for the embedded space that space and that technologies like ARM's Jazelle make it very efficient for their CPUs to run Java bytecode? – Blrfl May 13 '15 at 20:39
  • See also: http://stackoverflow.com/q/20103318/377657 – rwong May 13 '15 at 23:36
  • @smeeb: The reason why it is inefficient to send unmodified JVM bytecode to hardware execution is that it is unable to take advantage of JIT (just-in-time optimization). To improve execution performance, changes must be made to the instruction stream. For example, memoization is used to avoid repeated computations if every time the result is known to be the same. To do this (compute once and reuse the value), code motion is required, so the instruction stream needs to be modified. Dead code elimination is another. – rwong May 13 '15 at 23:44
  • @smeeb: For CPU architectures that have a clean design (emphasizing malleability of the RTL design, instead of optimizing the last bit of juice from silicon-level tweaks), it is very doable to add additional "acceleration instructions" that will improve the performance of an integrated JVM host. However, to take advantage of that, the JVM host obviously need to incorporate code changes (basically to replace some of its operations with the hardware-accelerated instructions). Unfortunately, every time a vendor does this, it makes the JVM code base less maintainable. – rwong May 13 '15 at 23:49
  • 1
    @smeeb: a device that can directly execute Java byte code isn't really an ASIC anymore, it's a full blown microprocessor. At that kind of complexity, why limit yourself to only Java code, when the embedded market is dominated by software written in C and assembly? – whatsisname May 14 '15 at 04:37
  • If you want an executable binary, you should probably look into a compiled language like Go. – Thorbjørn Ravn Andersen Jan 31 '19 at 01:22

1 Answers1

3

You can compile Java to binary for a target using LLVM. A quick look at the docs shows support for many ARM variants.

https://llvm.org/svn/llvm-project/java/trunk/docs/java-frontend.txt

Also another stack-overflow style answer in this vein at

https://stackoverflow.com/questions/10804280/is-there-a-llvm-java-front-end-that-converts-java-source-to-llvms-intermediate

Tim Williscroft
  • 3,563
  • 1
  • 21
  • 26