Dalvik

The Dalvik virtual machine is intended to run on a variety of platforms.The baseline system is expected to be a variant of UNIX (Linux, BSD, MacOS X) running the GNU C compiler. Little-endian CPUs have been exercisedthe most heavily, but big-endian systems are explicitly supported.

There are two general categories of work: porting to a Linux systemwith a previously unseen CPU architecture, and porting to a differentoperating system. This document covers the former.

Core Libraries

The native code in the core libraries (chiefly dalvik/libcore,but also dalvik/vm/native) is written in C/C++ and is expectedto work without modification in a Linux environment. Much of the codecomes directly from the Apache Harmony project.

The core libraries pull in code from many other projects, includingOpenSSL, zlib, and ICU. These will also need to be ported before the VMcan be used.

JNI Call Bridge

Most of the Dalvik VM runtime is written in portable C. The onenon-portable component of the runtime is the JNI call bridge. Simply put,this converts an array of integers into function arguments of varioustypes, and calls a function. This must be done according to the C callingconventions for the platform. The task could be as simple as pushing allof the arguments onto the stack, or involve complex rules for registerassignment and stack alignment.

To ease porting to new platforms, the open-source FFI library (Foreign Function Interface) is used when acustom bridge is unavailable. FFI is not as fast as a native implementation,and the optional performance improvements it does offer are not used, sowriting a replacement is a good first step.

The code lives in dalvik/vm/arch/*, with the FFI-based versionin the "generic" directory. There are two source files for each architecture.One defines the call bridge itself:

void dvmPlatformInvoke(void* pEnv, ClassObject* clazz, int argInfo,int argc, const u4* argv, const char* signature, void* func,JValue* pReturn)

This will invoke a C/C++ function declared:

return_type func(JNIEnv* pEnv, Object* this [, args])
or (for a "static" method):
return_type func(JNIEnv* pEnv, ClassObject* clazz [, args])

The role of dvmPlatformInvoke is to convert the values inargv into C-style calling conventions, call the method, andthen place the return type into pReturn (a union that holdsall of the basic JNI types). The code may use the method signature(a DEX "shorty" signature, with one character for the return type and oneper argument) to determine how to handle the values.

The other source file involved here defines a 32-bit "hint". The hintis computed when the method's class is loaded, and passed in as the"argInfo" argument. The hint can be used to avoid scanning the ASCIImethod signature for things like the return value, total argument size,or inter-argument 64-bit alignment restrictions.

Interpreter

The Dalvik runtime includes two interpreters, labeled "portable" and "fast".The portable interpreter is largely contained within a single C function,and should compile on any system that supports gcc. (If you don't have gcc,you may need to disable the "threaded" execution model, which relies ongcc's "goto table" implementation; look for the THREADED_INTERP define.)

The fast interpreter uses hand-coded assembly fragments. If none areavailable for the current architecture, the build system will create aninterpreter out of C "stubs". The resulting "all stubs" interpreter isquite a bit slower than the portable interpreter, making "fast" somethingof a misnomer.

The fast interpreter is enabled by default. On platforms without nativesupport, you may want to switch to the portable interpreter. This canbe controlled with the dalvik.vm.execution-mode systemproperty. For example, if you:

adb shell "echo dalvik.vm.execution-mode = int:portable >> /data/local.prop"

and reboot, the Android app framework will start the VM with the portableinterpreter enabled.

Mterp Interpreter Structure

There may be significant performance advantages to rewriting theinterpreter core in assembly language, using architecture-specificoptimizations. In Dalvik this can be done one instruction at a time.

The simplest way to implement an interpreter is to have a large "switch"statement. After each instruction is handled, the interpreter returns tothe top of the loop, fetches the next instruction, and jumps to theappropriate label.

An improvement on this is called "threaded" execution. The instructionfetch and dispatch are included at the end of every instruction handler.This makes the interpreter a little larger overall, but you get to avoidthe (potentially expensive) branch back to the top of the switch statement.

Dalvik mterp goes one step further, using a computed goto instead of a gototable. Instead of looking up the address in a table, which requires anextra memory fetch on every instruction, mterp multiplies the opcode numberby a fixed value. By default, each handler is allowed 64 bytes of space.

Not all handlers fit in 64 bytes. Those that don't can have subroutinesor simply continue on to additional code outside the basic space. Some ofthis is handled automatically by Dalvik, but there's no portable way to detectoverflow of a 64-byte handler until the VM starts executing.

The choice of 64 bytes is somewhat arbitrary, but has worked out well forARM and x86.

In the course of development it's useful to have C and assemblyimplementations of each handler, and be able to flip back and forthbetween them when hunting problems down. In mterp this is relativelystraightforward. You can always see the files being fed to the compilerand assembler for your platform by looking in thedalvik/vm/mterp/out directory.

The interpreter sources live in dalvik/vm/mterp. If youhaven't yet, you should read dalvik/vm/mterp/README.txt now.

Getting Started With Mterp

Getting started:

  1. Decide on the name of your architecture. For the sake of discussion,let's call it myarch.
  2. Make a copy of dalvik/vm/mterp/config-allstubs todalvik/vm/mterp/config-myarch.
  3. Create a dalvik/vm/mterp/myarch directory to hold yoursource files.
  4. Add myarch to the list indalvik/vm/mterp/rebuild.sh.
  5. Make sure dalvik/vm/Android.mk will find the files foryour architecture. If $(TARGET_ARCH) is configured thiswill happen automatically.

You now have the basic framework in place. Whenever you make a change, youneed to perform two steps: regenerate the mterp output, and build thecore VM library. (It's two steps because we didn't want the build systemto require Python 2.5. Which, incidentally, you need to have.)

  1. In the dalvik/vm/mterp directory, regenerate the contentsof the files in dalvik/vm/mterp/out by executing./rebuild.sh. Note there are two files, one in C and onein assembly.
  2. In the dalvik directory, regenerate thelibdvm.so library with mm. You can also usemake libdvm from the top of the tree.

This will leave you with an updated libdvm.so, which can be pushed out toa device with adb sync or adb push. If you'reusing the emulator, you need to add make snod (System image,NO Dependency check) to rebuild the system image file. You should notneed to do a top-level "make" and rebuild the dependent binaries.

At this point you have an "all stubs" interpreter. You can see how itworks by examining dalvik/vm/mterp/cstubs/entry.c. Thecode runs in a loop, pulling out the next opcode, and invoking thehandler through a function pointer. Each handler takes a "glue" argumentthat contains all of the useful state.

Your goal is to replace the entry method, exit method, and each individualinstruction with custom implementations. The first thing you need to dois create an entry function that calls the handler for the first instruction.After that, the instructions chain together, so you don't need a loop.(Look at the ARM or x86 implementation to see how they work.)

Once you have that, you need something to jump to. You can't branchdirectly to the C stub because it's expecting to be called with a "glue"argument and then return. We need a C stub "wrapper" that does thesetup and jumps directly to the next handler. We write this in assemblyand then add it to the config file definition.

To see how this works, create a file calleddalvik/vm/mterp/myarch/stub.S that contains one line:

 
/* stub for ${opcode} */
Then, in dalvik/vm/mterp/config-myarch, add this below the handler-size directive:
 
# source for the instruction table stub
asm-stub myarch/stub.S

Regenerate the sources with ./rebuild.sh, and take a lookinside dalvik/vm/mterp/out/InterpAsm-myarch.S. You shouldsee 256 copies of the stub function in a single large block after thedvmAsmInstructionStart label. The stub.S code will be used anywhere you don't provide an assembly implementation.

Note that each block begins with a .balign 64 directive.This is what pads each handler out to 64 bytes. Note also that the${opcode} text changed into an opcode name, which shouldbe used to call the C implementation (dvmMterp_${opcode}).

The actual contents of stub.S are up to you to define.See entry.S and stub.S in the armv5te or x86 directories for working examples.

If you're working on a variation of an existing architecture, you may beable to use most of the existing code and just provide replacements fora few instructions. Look at the armv4t implementation asan example.

Replacing Stubs

There are roughly 230 Dalvik opcodes, including some that are inserted bydexopt and aren't described in theDalvik bytecode documentation. Eachone must perform the appropriate actions, fetch the next opcode, andbranch to the next handler. The actions performed by the assembly versionmust exactly match those performed by the C version (indalvik/vm/mterp/c/OP_*).

It is possible to customize the set of "optimized" instructions for yourplatform. This is possible because optimized DEX files are not expectedto work on multiple devices. Adding, removing, or redefining instructionsis beyond the scope of this document, and for simplicity it's best to stickwith the basic set defined by the portable interpreter.

Once you have written a handler that looks like it should work, addit to the config file. For example, suppose we have a working versionof OP_NOP. For demonstration purposes, fake it for now byputting this into dalvik/vm/mterp/myarch/OP_NOP.S:

 
/* This is my NOP handler */

Then, in the op-start section of config-myarch, add:

 
    op OP_NOP myarch

This tells the generation script to use the assembly version from themyarch directory instead of the C version from the c directory.

Execute ./rebuild.sh. Look at InterpAsm-myarch.S and InterpC-myarch.c in the out directory. Youwill see that the OP_NOP stub wrapper has been replaced with ournew code in the assembly file, and the C stub implementation is no longerincluded.

As you implement instructions, the C version and corresponding stub wrapperwill disappear from the output files. Eventually you will have a 100%assembly interpreter.

Interpreter Switching

The Dalvik VM actually includes a third interpreter implementation: the debuginterpreter. This is a variation of the portable interpreter that includessupport for debugging and profiling.

When a debugger attaches, or a profiling feature is enabled, the VMwill switch interpreters at a convenient point. This is done at thesame time as the GC safe point check: on a backward branch, a methodreturn, or an exception throw. Similarly, when the debugger detachesor profiling is discontinued, execution transfers back to the "fast" or"portable" interpreter.

Your entry function needs to test the "entryPoint" value in the "glue"pointer to determine where execution should begin. Your exit functionwill need to return a boolean that indicates whether the interpreter isexiting (because we reached the "bottom" of a thread stack) or wants toswitch to the other implementation.

See the entry.S file in x86 or armv5te for examples.

Testing

A number of VM tests can be found in dalvik/tests. The mostuseful during interpreter development is 003-omnibus-opcodes,which tests many different instructions.

The basic invocation is:

 
$ cd dalvik/tests
$ ./run-test 003

This will run test 003 on an attached device or emulator. You can runthe test against your desktop VM by specifying --reference if you suspect the test may be faulty. You can also use--portable and --fast to explictly specifyone Dalvik interpreter or the other.

Some instructions are replaced by dexopt, notably when"quickening" field accesses and method invocations. To ensurethat you are testing the basic form of the instruction, add the--no-optimize option.

There is no in-built instruction tracing mechanism. If you wantto know for sure that your implementation of an opcode handleris being used, the easiest approach is to insert a "printf"call. For an example, look at common_squeak indalvik/vm/mterp/armv5te/footer.S.

At some point you need to ensure that debuggers and profiling work withyour interpreter. The easiest way to do this is to simply connect adebugger or toggle profiling. (A future test suite may include sometests for this.)


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值