The Source for Java Technology Collaboration
User: Password:



Mark Lam's Blog

December 2006 Archives


A Field Get Experience

Posted by mlam on December 14, 2006 at 06:41 PM | Permalink | Comments (0)

This article is a continuation of my series of discussions about the internals of the phoneME Advanced VM (commonly known as CVM) for JavaME CDC. Below, I'll work on fixing a bug in the VM. Along the way, I'll discuss more of CVM's internal mechanisms. Note: for the purpose of this discussion, I will only focus on the coding aspects. The source code version control details will not be discussed here.

Resources: start of CVM internals discussion, copy of the CVM map, PDF of map for printing, .h files in src/share/javavm/include, and .c files in src/share/javavm/runtime.

What is the Bug?
The bug report can be found here (bug 6450163). Essentially, reads and writes to long and double volatile variables need to be atomic. This is specified in section 17.7 (Rules for Volatile Variables) of the Java Language Specification:

The load, store, read, and write actions on volatile variables are atomic, even if the type of the variable is double or long.

Currently, the JIT handles this by refusing to compile methods that accesses these types of 64-bit variables. Instead it defers execution of the method to the interpreter which already handles these 64-bit volatile field accesses in an atomic fashion. The bug is one of performance rather than of correctness.

Digging further ...

Continue Reading...



Beware of the Natives

Posted by mlam on December 07, 2006 at 02:11 PM | Permalink | Comments (16)

There are a lot of not so nice things about using native methods. Here are some:

  • less safe - think "stray pointers".
  • less portable - you'll have to recompile them for every target device architecture you deploy on, present and future.
  • less cost effective - need extra work to build and test all the architecture variations, extra disk storage for deploying all the different binary versions, etc.
  • less manageable - can be a "binary version" tracking, management, and device provisioning nightmare.

But if these reasons aren't enough to deter you from using native methods, try this on for size:

Native code can hurt performance

This seems to go against most people's expectations, but it is the truth. First of all, there is the reason due to what goes on in the runtime stacks when you invoke native methods. I've talked about that in my previous articles (here, here, and here). There, I showed that using native methods incurs bootstrapping and extra frame pushing/popping overhead which results in degraded performance. But there are also many other reasons besides this.

To be fair, native code can be used to help improve performance when used in the right places. I will explain those cases as well. The key is to use native code "carefully".

Ok, let's go bust the "native" myth ...

Continue Reading...



JIT me up, Scotty

Posted by mlam on December 06, 2006 at 07:03 PM | Permalink | Comments (1)

Map of CVM Data Structures

The phoneME Advanced VM (CVM) comes with a dynamic adaptive compiler (JIT) which generates compiled code. Today's article will talk about how JIT compiled code uses the runtime execution stacks. I will also point out a few other tidbits about efficiency and performance as pertaining to the runtime stacks.

Resources: start of CVM data structure discussion, start of stacks discussion, copy of the map, PDF of map for printing, .h files in src/share/javavm/include, and .c files in src/share/javavm/runtime.

Let's get started ...

Bouncing on the Compiled Code Trampoline
A colleague of mine once describe the compiled code generated by CVM's JIT as co-routines. Unlike C functions, they don't have their own native stack frame. Instead, compiled code is bootstrapped using a piece of assembler glue called CVMgoNative (which I will abbreviate as goNative for the purpose of this discussion). goNative sets up a stack frame and reserves some small amount of scratch meory on the native stack for the use of any compiled code. After that, it branches into the appropriate entry point in the compiled code of the method to be invoked. For example, when we have an interpreted method (mI) call a compiled method (mC), the runtime stacks looks like this:

native stack: ... executeJava -> goNative
Java stack: ... mI -> mC

Let's take a look at another case. Here's compiled (mCa) to compiled (mCb):

native stack: ... executeJava -> goNative
Java stack: ... mCa -> mCb

Note that even if the very first Java method executed is a compiled method, we still have an instance of executeJava (i.e. the interpreter loop) on the native stack. This is because like with native code, the interpreter is used to do the bootstrapping via transition methods (see previous discussion for details).

Once we're executing in compiled code, the VM will tend to stay in compiled code until there is a need to exit. Here's interpreted (mIa) to compiled (mCa) to compiled (mCb) to compiled (mCc):

native stack: ... executeJava -> goNative
Java stack: ... mIa -> mCa -> mCb -> mCc

Note that there is only one instance of executeJava and goNative on the native stack even though there is 1 interpreted and 3 compiled methods being executed. CVM first enters executeJava and interprets method mIa. When m1a calls mCa, the interpreter detects that mCa is compiled. So, it pushes a compiled frame (see CVMCompiledFrame in interpreter.h) onto the Java stack instead. Next, it calls goNative with the appropriate entry point into mCa.

When mCa calls mCb, the compiled code will make use of some assembler code called the invoke glue (see examples in src/arm/javavm/runtime/jit/ccminvokers_cpu.S here). The invoke glue then branches to the entry point for mCb. mCb does not get another frame on the native stack, but reuses the same scratch memory allocated by goNative that was previously used by mCa. Hence, the scratch memory on the native stack is not used to hold state information across method call boundaries. All method state that need to persist across this boundary is kept in the compiled frame on the Java stack instead (see here for the structure of the compiled frame).

This is what we mean by the "staying in compiled code". Though code execution goes from one method to another, there is no new native frame being pushed or popped, and code flow does not go through the interpreter loop. Overhead between method calls are kept to a minimum.

Each compiled method looks like a routine that we branch to instead of calling. The code flow execution pattern looks like bouncing on a trampoline. We bounce off the glue trampoline to jump into a compiled method. To call another method, we fall out of that method back into the glue, and bounce into another compiled method. The same is done for returns as well as invocations. Hence, the trampoline analogy. Sometimes, the glue code is referred to as trampoline code.

But what if we need to call an interpreted method from compiled code?

Continue Reading...



A Tale of Two Stacks

Posted by mlam on December 05, 2006 at 01:10 AM | Permalink | Comments (0)

Map of CVM Data Structures There's a term that I commonly use to describe CVM: embedded friendliness. Is this really a proper English term? Maybe, or maybe not. But it concisely expresses the idea that a system is suitably designed to work on embedded systems, and thereby has the property of embedded friendliness. And CVM is embedded friendly. If you are a purist of the English language, I apologize. But for the sack of conciseness, I'll continue using this term.

So today, I'll continue talking about the runtime stacks in the phoneME Advanced VM / CVM from my last entry.

Resources: start of CVM data structure discussion, copy of the map, PDF of map for printing, .h files in src/share/javavm/include, and .c files in src/share/javavm/runtime.

Let's get started ...

Why use 2 Stacks?
Obviously, we cannot do without a native stack. The native thread depends on this stack. So, the question can be rephrased as why use a separate Java stack instead of just pushing Java frames on the native stack like JavaSE? I think the reason is because CVM was designed to be as friendly as possible for embedded OSes as well.

In the early days of CVM, linux was still uncommon in embedded systems. Most embedded OSes in those days (and still today) do not have processes, and do not provide access to a virtual memory manager (even if the hardware supports it). Hence, when you create a native thread, you will have to malloc a chunk of memory for the use of the native stack. Remember that CVM's threads are mapped directly onto native threads. One issue with having to allocate memory for the stacks is knowing exactly how much memory to allocate. If you allocate too little, applications won't run. Allocate too much, and you'll have wasted resources. For embedded devices, wasting resources is highly undesirable. Note that without virtual memory, malloc'ing the memory here means a fixed sized allocation and the memory is committed upon allocation. In turn, that means no other thread can use the memory even if the current thread doesn't need it. This is why over-allocating stack sizes results in wastage for most embedded OSes.

For classical embedded software written in C, one could do some analysis to determine max usage of stack space and get an optimum allocation of memory. For a Java platform, this is typically not possible. One of the most desirable features of the Java platform is its ability to enhance the device by allowing new or updated versions of applications to be downloaded and executed without having to re-ROM/flash the core firmware in the device. This means that the stack requirement of the application(s) is not known / computable at the time the Java VM is built.

And the solution is ...

Continue Reading...





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds