iOS Port

1,249 views
Skip to first unread message

mzechner

unread,
Jul 29, 2011, 5:36:36 PM7/29/11
to Avian
Hi there,

i contacted Joel in private a few days ago and of course he prefers to
have a public conversation about the given topic. Sorry Joel, i should
have known better. I give the exact same answer to people that bug me
via e-mail about the framework :/

I'll cut and paste our convo in in-line style and end with a few
follow up comments.

[ME]
my name is Mario Zechner, i'm the creator of libgdx [1], a Java/C/C++
based cross-platform game development framework for the desktop and
Android. I've already infiltrated the Avian VM Google group. I hope
you don't mind me writting you directly. I'll try to keep it short
(but will most likely fail, sorry :/).

[JOEL]
I don't mind you writing me directly, but I do prefer to answer
questions about Avian on the Google group so others can benefit. That
way, I don't have to repeat myself if others have the same questions,
and the archives are available to anyone searching the Web for Avian-
related information.

I'm sure you're not the only one interested in using Avian on iOS.
[CUT SOMETHING HERE AS I DON'T KNOW IF ITS CONFIDENTIAL] So, I would
prefer that any further discussion happen on the group. Plus, I know
that there are some Apple experts subscribed to it who may be able to
help you out better than I can.

[ME]
First of all i have to say that i was highly impressed with what you
did with Avian. It's a very impressive and well executed project,
exactly the kind of lightweight yet capable JVM i've been looking for.

What i'm interested in the most is the AOT capabilities of Avian.
Cutting a long story short: i want to AOT compile Java bytecode to
Mach-o to be able to (statically) link it against iOS binaries. This
means i need the following:

- AOT compilation of byte code to a Mach-o container for ARMv6
(preferring the later of course), using the iOS ABI [2] (which seems
to be standard). binaryToObject seems to support that for a couple of
platforms, but not for ARM.

[JOEL]
Adding ARM support to mach-o.cpp should not be difficult. If you look
at that file, you'll see that most of the code is architecture-
agnostic.

The more challenging part might be porting arm.S, arm.h, and arm.cpp,
which currently support Linux only. I haven't looked at how the iOS
ABI compares to the ABI we use for Linux. I know the Linux and OS X
PowerPC ABIs are different enough that it was non-trivial to add
support for the former after we already had the latter working.

[ME]
- a runtime environment that allows me to execute the AOT compiled
bytecode (gc, locks & threading, a minimal runtime lib etc., basically
anything a JVM would provide for JIT compiled/interpreted code
execution). Avian seems to provide just that, in a nice cross-platform
way. Since there's a posix and darwin backend i'd assume only minimal
modifications would be necessary.

[JOEL]
I don't know much about iOS, but a quick look at the Wikipedia page
indicates that it shares its Darwin foundation with OS X and only
really differs at the UI level. In that case, there should be little
or no porting needed beyond the architecture- and ABI-specific parts I
mentioned above.

[ME]
- access to JNI methods, statically linked to the AOT compiled Mach-o
object file. That seems to be possible as well with some JNIEXPORT
magic if i understood things correctly.

[JOEL]
Yes, that should be easy if iOS at least allows dlsym calls to the
current executable. Otherwise, we'll have to get creative (e.g. build
a table of function pointers by hand or via code generation).

[ME]
So, apart from the missing supported vor ARM + Mach-o, everything
seems to be covered. Here are a few things i'm worried about:

- iOS is very restrictive in terms of dynamic loading, read: it
doesn't allow shared libs nor does it allow setting memory to be
executeable. With a bootimage build, would i need the later? It seems
to me that the AOT compiled code's symbols are directly exported
through the container file formats like ELF or Mach-o, i might be
wrong though. My knowledge of these things is very rusty so i couldn't
make 100% sense of the code i checked.

[JOEL]
If everything's in the bootimage, and iOS respects the readable/
writable/executable flags on the Mach-O segment in which the bootimage
resides, it should work fine. As I said above, though, if it doesn't
even allow dlsym calls to the current executable, we'll have to do
something special to make JNI work.

[ME]
- As oposed to other projects like Mono et. al you seem to have your
own GC instead of the Boehm-Demers-Weiser GC that's commonly used. Is
there any platform specific trickery involved that would prevent the
GC from working on iOS?

[JOEL]
There's no trickery there, so I would not expect any problems. The
Boehm GC must use all kinds of platform-specific trickery because it
needs to search for root references anywhere they might exist with
little or no help from the application or VM. That means it must find
the extent of each thread's stack, know how to force the contents all
relevant registers to memory, etc. Avian's GC expects the VM to tell
it where all the root references are, and the VM is designed such that
those references are all tracked explicitly; there's no need to search
for them.

[ME]
- What ABI do you target with the ARM backend?

[JOEL]
I'm not really sure what it's even called :) It's whatever modern
Linux-on-ARM systems use. Some call it the GNU EABI, but I take it
that's not the official name. Here are some links to the relevant
specifications:

http://wiki.debian.org/ArmEabiPort#References

Someone else did most of the work on the ARM port, so I'm not really
an expert. For the parts I contributed, I relied heavily on the
assembly output from GCC to answer questions about the ABI rather than
look at specifications.

[ME]
- Is there support for VFP or do you use softfp?

[JOEL]
The ARM port of the JIT compiler (which is also used for AOT
compilation) defers to out-of-line C++ helper functions for all
floating point ops. Whether those helper functions use hardware or
software FP depends on the compiler/flags used to build Avian.

[ME]
I'd appreciate any pointers you could throw my way. I've looked at
various alternatives, and the thing that draws me the most to Avian is
it's minimal runtime library as well as it's comparably small
code-base. I'm aware that it might not be production ready yet, but i
think i could cope with that if i was able to get an iOS port going.

[JOEL]
FWIW, we've been deploying Avian-based products at my company for over
a year now, so it's production ready as far as our needs go.

[ME]
I'd love to collaborate, even though you might not want C++ code
contributions from me. My brain's been poisoned with Java over the
last couple of years, and my C++ knowledge (and the little ARM
assembler knowledge i had) have mostly been replaced with hideous EE
framework nightmares. I'd still offer my help were possible, be it in
form of code or testing.

[JOEL]
The lack of comments/documentation in e.g. arm.cpp and the rest of the
VM would probably be the biggest hurdle even if you were a C++ expert.
That's something I hope to improve eventually, but writing
documentation isn't much fun :) Anyway, if you want to get started on
this, I'll be happy to help as time allows. I might start tinkering
with it myself if I can get an iOS dev/test environment set up.

[ME]
Another thing i could provide is a community full of people that would
love to help testing Avian, on iOS but potentially also on other
platforms. If Avian turns out to be stable enough it could be a nice
alternative for people that want their games to run "natively",
without the need for an installed JRE. Most of the games produced by
the community do not demand that much CPU-side performance, so even if
Avian can't reach HotSpot in terms of execution speed, it would still
be more than good enough for 99% of those games.

Finally I'd like to ask what your plans are in terms of potential
commercialization of Avian. I provide virtually all my non-day-job
code under a very permissive OSS licence (Apache 2) so other's can
benefit from it. I generate income by support contracts, workshops and
the like. Getting those gigs is merely a question of reputation. A
successful OSS project can get you that.

[JOEL]
I have no commercial plans. I'm just in it for the technical
challenge and learning opportunities. My day job pays the bills, and
I even get to work on Avian on company time since it's become an
essential piece of infrastructure for us.

[ME]
An iOS port of Avian that is compatible with the Appstore TOS could be
a huge seller of course, so i'd understand if you'd go a non OSS or
potentially (A)GPL road in that case. If you decided to keep the
current licence you'd make a ton of people happy though :)

[JOEL]
We'll definitely stick with the current license.

[ME]
Thanks if you read that far, this thing got longer than intended.
Whatever the outcome with regards to the above things, keep up the
insanely good work. I'll definitely give Avian a try for a couple of
projects of mine on the desktop.

[JOEL]
Thanks for the feedback. libgdx looks like a cool project, and I
agree that Avian would be a good choice to make it run on iOS. I'm
definitely curious to find out how much work the port would require.

FOLLOW-UP
Thanks to Joel for taking the time and answer my lengthy inquiry. Here
are the things that would need to be resolved:

- check ARM version used. ARMv6 would be preferred. Didn't have time
to look into that yet.
- check VFP support. Currently Avian calls out to C methods for all
floating point related functionality. That might actually be a good
thing in case of iOS. Though it incurs a function call as i understood
it, there's an oportunity to go from softfp to VFP and eventually also
have a NEON backend.
- check current ARM ABI against iOS ABI. I had a quick look and there
seem to be a few differences, nothing to worrying though.
- check dlsym availability. I couldn't find hard facts on this matter,
but from the looks of it it is at least possible to load symbols from
statically linked object files on iOS.
- extent mach-o support to ARM.

One thing i thought about: an alternative to outputting object files/
boot images would be to actually output an intermediate
representation, either C or LLVM IL. What i've seen so far that might
actually be possible. This way one could use the LLVM toolchain XCode
uses for iOS to compile the final binary which might make some things
easier. Input on that would be appreciated.

I'm currently moving to SF and will have a bit of time over the next 3
months. I'm not sure i'm up for the task (and i also lack a mac dev
environment) but i think it would be an awesome side-project.

Sorry for the mail-bomb, and thanks Joel for providing us with such an
awesome project!

mzechner

unread,
Jul 29, 2011, 5:36:35 PM7/29/11
to Avian

Josh Marinacci

unread,
Jul 30, 2011, 1:28:46 PM7/30/11
to av...@googlegroups.com
I'd be interested in helping on the webOS port which would share a lot of code.



-- Sent from my HP Veer


--
You received this message because you are subscribed to the Google Groups "Avian" group.
To post to this group, send email to av...@googlegroups.com.
To unsubscribe from this group, send email to avian+un...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/avian?hl=en.

Joel Dice

unread,
Jul 30, 2011, 1:36:37 PM7/30/11
to av...@googlegroups.com
On Sat, 30 Jul 2011, Josh Marinacci wrote:

> I'd be interested in helping on the webOS port which would share a lot of
> code.

Since WebOS is based on Linux, it should already work (in theory),
assuming you have some way of building, installing and running native
executables on the device. Have you tried it?

Joel Dice

unread,
Aug 11, 2011, 11:21:46 AM8/11/11
to Avian, badlog...@gmail.com
Hi all,

Good news regarding the iOS port: I've got all but one of the tests
passing on an jailbroken iPhone 3G I'm borrowing from a colleague. The
one test that isn't passing is the OutOfMemory test, which causes the
phone to lock up and eventually reboot itself every time I run it, even
when I reduce the VM heap size from the default 128MB to 8MB. That makes
it hard to debug, but I'll try narrow down the problem when I have time.

The code is in the "ios" branch of the Git repository. Bootimage (i.e.
ahead-of-time-compiled) builds are not yet supported because Avian can't
currently build a bootimage targeting a different architecture than the
build system, and I haven't tried installing GCC on the iPhone to do
native builds. The lack of cross-architecture build support for
bootimages is something I've wanted to address for a while, and this may
be a good time to finally do it.

Josh Marinacci

unread,
Aug 23, 2011, 3:17:31 PM8/23/11
to av...@googlegroups.com
I've just tried to compile Avian. I got it working fine for MacOSX, but when I use my cross compiler it says it can't find the right gcc for arm. By overriding the cc var I can get a bit further, but now it seems to be looking for missing headers or libs. How should I tell Avian to use an alternate gcc location?

localhost:avian joshmarinacci$ rm -rf build; make platform=linux arch=arm cc=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-gcc
compiling build/linux-arm/type-generator-build.o
compiling build/linux-arm/posix-build.o
compiling build/linux-arm/finder-build.o
linking build/linux-arm/generator
Undefined symbols:
"_CFStringGetMaximumSizeOfFileSystemRepresentation", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"_CFBundleGetMainBundle", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"_kCFAllocatorDefault", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"_CFURLCopyPath", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"___CFConstantStringClassReference", referenced from:
cfstring= in posix-build.o
"_CFBundleCopyExecutableURL", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"_CFStringGetFileSystemRepresentation", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"_CFURLCreateStringByReplacingPercentEscapes", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
make: *** [build/linux-arm/generator] Error 1
localhost:avian joshmarinacci$

thanks,
Josh

> --
> You received this message because you are subscribed to the Google Groups "Avian" group.
> To post to this group, send email to av...@googlegroups.com.
> To unsubscribe from this group, send email to avian+un...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/avian?hl=en.
>

Blasting forth in three part harmony!

Joel Dice

unread,
Aug 23, 2011, 3:31:38 PM8/23/11
to av...@googlegroups.com
Hi Josh,

I haven't tried cross compiling for Linux/ARM on OS X myself, but it
should be doable. If you look in the makefile, you'll find a section that
looks like this:

ifeq ($(arch),arm)
asm = arm
pointer-size = 4
cflags += -marm -Wno-psabi

ifneq ($(arch),$(build-arch))
cxx = arm-linux-gnueabi-g++
cc = arm-linux-gnueabi-gcc
ar = arm-linux-gnueabi-ar
ranlib = arm-linux-gnueabi-ranlib
strip = arm-linux-gnueabi-strip
endif
endif

If you change the arm-linux-gnueabi-* commands to e.g.
/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-*, that should do the
trick. You can also override cxx, cc, ar, ranlib, and strip on the
command line if you want to avoid editing the makefile. You might also
want to set MAKEFLAGS= (i.e. set it to nothing) in order to see what
commands are actually being run:

make platform=linux arch=arm MAKEFLAGS= \
cxx=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-g++ \
cc=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-gcc \
ar=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-ar \
ranlib=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-ranlib \
strip=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-strip

Please let me know if you still have trouble.

Josh Marinacci

unread,
Aug 23, 2011, 4:30:33 PM8/23/11
to av...@googlegroups.com
Thanks!

I'm still getting undefined symbols. Forgive me for my newbie questions. I'm a Java expert, not a C guy. If it can't find symbols then there must be a missing path for the libraries, right? Where to -lz -lpthread, etc. map to?

- J

linking build/linux-arm/generator
gcc build/linux-arm/type-generator-build.o build/linux-arm/posix-build.o build/linux-arm/finder-build.o -lz -lpthread -ldl -o build/linux-arm/generator


Undefined symbols:
"_CFStringGetMaximumSizeOfFileSystemRepresentation", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"_CFBundleGetMainBundle", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"_kCFAllocatorDefault", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"_CFURLCopyPath", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"___CFConstantStringClassReference", referenced from:
cfstring= in posix-build.o
"_CFBundleCopyExecutableURL", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"_CFStringGetFileSystemRepresentation", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
"_CFURLCreateStringByReplacingPercentEscapes", referenced from:
(anonymous namespace)::pathOfExecutable(vm::System*, char const**, unsigned int*)in posix-build.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
make: *** [build/linux-arm/generator] Error 1

Joel Dice

unread,
Aug 23, 2011, 7:09:42 PM8/23/11
to av...@googlegroups.com
On Tue, 23 Aug 2011, Josh Marinacci wrote:

> Thanks!
>
> I'm still getting undefined symbols. Forgive me for my newbie questions.
> I'm a Java expert, not a C guy. If it can't find symbols then there must
> be a missing path for the libraries, right? Where to -lz -lpthread,
> etc. map to?

No problem about the questions. Building cross-platform C/C++ code is
messy and difficult even for those with lots of experience.

I should have noticed in your first email that it's breaking when building
the the type generator utility, which is supposed to be a native (OS X, in
this case) executable, so we haven't even reached the cross compilation
stage. It's breaking because the makefile currently assumes the build
platform will only be OS X if the target platform is too, which obviously
doesn't hold in your case.

Anyway, I just tried it myself and got it building with a few changes to
the makefile, which I've committed to the "ios" branch of the Git
repository. Please check out that branch and use this command:

make platform=linux arch=arm MAKEFLAGS= \
cxx=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-g++ \
cc=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-gcc \
ar=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-ar \
ranlib=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-ranlib \

strip=/opt/PalmPDK/arm-gcc/bin/arm-none-linux-gnueabi-strip \
extra-cflags=-I/opt/PalmPDK/include/ \
extra-lflags=-L/opt/PalmPDK/device/lib/

Josh Marinacci

unread,
Aug 24, 2011, 12:54:27 PM8/24/11
to av...@googlegroups.com
sweet. that worked. I'll let you know more once I test it.
-j

Joel Dice

unread,
Sep 1, 2011, 1:37:13 PM9/1/11
to Avian, badlog...@gmail.com
On Thu, 11 Aug 2011, Joel Dice wrote:

> The code is in the "ios" branch of the Git repository. Bootimage (i.e.
> ahead-of-time-compiled) builds are not yet supported because Avian can't
> currently build a bootimage targeting a different architecture than the build
> system, and I haven't tried installing GCC on the iPhone to do native builds.
> The lack of cross-architecture build support for bootimages is something I've
> wanted to address for a while, and this may be a good time to finally do it.

I went ahead and implemented this, so you can now do AOT bootimage builds
for iOS on OS X.

mzechner

unread,
Sep 6, 2011, 3:12:02 AM9/6/11
to Avian
And for completness, here's the ObjectiveC/C/C++ nightmare i compile
for iOS http://codepad.org/2oAY27O1

mzechner

unread,
Sep 6, 2011, 2:59:24 AM9/6/11
to Avian
I gave the latest ios branch a try. There seems to be a problem with
bootimages on iOS which i couldn't figure out yet. A bit of info:

- i'm trying this on an unrooted 3G with iOS 4.2.1 on it.
- i use the latest xcode with the 4.2 SDK (makefile assumed 4.3, i
changed that, among other things)
- i pretty much followed the guide on 'Proguard and bootimage',
without proguard, using the simple Hello.java file.
- compiling libavian and the bootimage for darwin/arm works out of the
box (make arch=arm bootimage=true)
- next i created a little script that takes the avian build and
creates a bootimage from that plus any .class file in the directory
specified b $1

#/bin/sh
export AVIAN=/Users/elias/workspace/avian/build/darwin-arm-bootimage
cd $1
cp -r $AVIAN/classpath/* .
cp $AVIAN/libavian.a .
ar x libavian.a
rm libavian.a
$AVIAN/bootimage-generator . bootimage.bin
$AVIAN/binaryToObject bootimage.bin bootimage.o
_binary_bootimage_bin_start _binary_bootimage_bin_end darwin arm 4
writable executable

I took the Hello.java example file, compiled it to a class file, put
it in a directory and executed the above script on that directory.
That seems to work just fine, the counts below look correct to me:
class count 386 string count 232 call count 433
heap size 736708 code size 305216

I then created a standard iOS application in xcode, linking the
bootimage.o and the rest of the extracted libavian object files to it.
Additionally i had to compile for armv6 only, and specify -rdynamic so
that any unused symbols stay in the resulting executable. The driver
is again the same as on the example page (the one for bootimages +
proguard). Sadly, that's where my problem begins.

The application compiles, links and starts as expected. Loading the
bootimage seems to work as well, the contents of it are the same as
the numbers output by binaryToObject. The
"Hello" class as well as the static "main" method are found as well.
When i finally try to invoke the method via e->CallStaticVoidMethod(),
with a single element String array as the argument, i get a
EXC_BAD_ACCESS. I backtraced it with gdb with the following output:

#0 0x0015f118 in _binary_bootimage_bin_start ()
#1 0x00012564 in .LvmInvoke_argumentTest () at bootimage-template.cpp:
7
#2 0x00012564 in .LvmInvoke_argumentTest () at bootimage-template.cpp:
7
#3 0x0003a444 in ~Protector [inlined] () at src/compile.cpp:8566
#4 0x00012564 in vmInvoke_returnAddress () at bootimage-template.cpp:
8566
#5 0x00012564 in .LvmInvoke_argumentTest () at bootimage-template.cpp:
7
#6 0x00003200 in vmRun () at bootimage-template.cpp:7
#7 0x0005bf80 in CallStaticVoidMethodV (t=<value temporarily
unavailable, due to optimizations>, m=<value temporarily unavailable,
due to optimizations>, a=0x2fdfddd0) at src/jnienv.cpp:1157
#8 0x000030f6 in JNIEnv_::CallStaticVoidMethod (this=0x995000,
cls=0x353080, methodID=0x1) at jni.h:1498
#9 0x00002dcc in -[aviantestAppDelegate
application:didFinishLaunchingWithOptions:] (self=0x351190,
_cmd=0x35ce3bbf, application=0x3272a0, launchOptions=0x0) at /Users/
elias/Desktop/aviantest/Classes/aviantestAppDelegate.mm:74

After familiarizing myself with all the concepts involved, i'd assume
that even with the bootimage approach, Avian has to set a memory
region to executable, correct? The above problem looks to be related
to that. My iPhone is not rooted, and setting memory to be executable
is forbidden on a stock iPhone. Could this be the problem? How could I
work around that (apart from rooting the phone)?

thanks a bunch for any pointers,
Mario


On Sep 1, 10:37 am, Joel Dice <joel.d...@gmail.com> wrote:

joel...@gmail.com

unread,
Sep 6, 2011, 7:49:16 PM9/6/11
to Avian
When the boot image is wrapped in a Mach-O object, it is placed in a special section called __rwx in a segment called __RWX.  Later, we pass "-Wl,-segprot,__RWX,rwx,rwx" to the linker to ensure that said section is made readable, writable, and executable when the executable is loaded.  My guess is that no such flag is passed to the linker in your XCode project, so the default permissions, which do not include executability, are used for the section.  If there's some way you can tell XCode to use that flag, it should fix your problem.  The VM makes no attempt to change memory permissions at runtime for AOT compiled code; it simply assumes the OS will respect the section permissions specified at link time.

mzechner

unread,
Sep 11, 2011, 11:57:02 PM9/11/11
to Avian
Thanks for the pointer, i added that linker flag with no success. I
checked the resulting mach-o executable, the segment is indeed marked
as read/write/executeable (using this handz tool http://sourceforge.net/projects/machoview/,
couldn't get the info from nm). I don't see any differences to other
code segments, e.g. TEXT, so i'm puzzled.

Any other ideas? If it's not too much to ask, could you maybe provide
a makefile for a simple bootimage app (e.g. Hello.java) that runs on
iOS i could dissect?

Thanks for all your help.

Joel Dice

unread,
Sep 13, 2011, 11:33:15 AM9/13/11
to Avian
On Sun, 11 Sep 2011, mzechner wrote:

> Thanks for the pointer, i added that linker flag with no success. I
> checked the resulting mach-o executable, the segment is indeed marked
> as read/write/executeable (using this handz tool http://sourceforge.net/projects/machoview/,
> couldn't get the info from nm). I don't see any differences to other
> code segments, e.g. TEXT, so i'm puzzled.

__TEXT should be readable and executable, but not writable, which is why
we can't use it for the bootimage. You can use "otool -lV <executable>"
to see the permissions for each segment.

> Any other ideas? If it's not too much to ask, could you maybe provide
> a makefile for a simple bootimage app (e.g. Hello.java) that runs on
> iOS i could dissect?

I don't have a makefile handy, but here are the steps I took to build such
an app, start to finish:

git clone git://oss.readytalk.com/avian.git
git checkout ios
make bootimage=true arch=arm
mkdir hello
cd hello
ar x ../build/darwin-arm-bootimage/libavian.a
mkdir stage1
cd stage1/
jar xf ../../build/darwin-x86_64-bootimage/classpath.jar
cd ..
cat >Hello.java <<EOF
public class Hello {
public static void main(String[] args) {
System.out.println("hello, world!");
}
}
EOF
javac -bootclasspath stage1 -d stage1 Hello.java
cat >hello.pro <<EOF
-keep class Hello {
public static void main(java.lang.String[]);
}
EOF
java -jar ../../proguard4.6beta1/lib/proguard.jar -injars stage1 \
-outjars stage2 @../vm.pro -dontusemixedcaseclassnames @hello.pro
../build/darwin-arm-bootimage/bootimage-generator stage2 bootimage.bin
../build/darwin-arm-bootimage/binaryToObject bootimage.bin \
bootimage-bin.o _binary_bootimage_bin_start _binary_bootimage_bin_end \
darwin arm 8 writable executable
cat >main.cpp <<EOF
#include "stdint.h"
#include "jni.h"

#if (defined __MINGW32__) || (defined _MSC_VER)
# define EXPORT __declspec(dllexport)
#else
# define EXPORT __attribute__ ((visibility("default")))
#endif

#if (! defined __x86_64__) && ((defined __MINGW32__) || (defined
_MSC_VER))
# define BOOTIMAGE_BIN(x) binary_bootimage_bin_##x
#else
# define BOOTIMAGE_BIN(x) _binary_bootimage_bin_##x
#endif

extern "C" {

extern const uint8_t BOOTIMAGE_BIN(start)[];
extern const uint8_t BOOTIMAGE_BIN(end)[];

EXPORT const uint8_t*
bootimageBin(unsigned* size)
{
*size = BOOTIMAGE_BIN(end) - BOOTIMAGE_BIN(start);
return BOOTIMAGE_BIN(start);
}

} // extern "C"

int
main(int ac, const char** av)
{
JavaVMInitArgs vmArgs;
vmArgs.version = JNI_VERSION_1_2;
vmArgs.nOptions = 1;
vmArgs.ignoreUnrecognized = JNI_TRUE;

JavaVMOption options[vmArgs.nOptions];
vmArgs.options = options;

options[0].optionString
= const_cast<char*>("-Davian.bootimage=bootimageBin");

JavaVM* vm;
void* env;
JNI_CreateJavaVM(&vm, &env, &vmArgs);
JNIEnv* e = static_cast<JNIEnv*>(env);

jclass c = e->FindClass("Hello");
if (not e->ExceptionCheck()) {
jmethodID m = e->GetStaticMethodID(c, "main",
"([Ljava/lang/String;)V");
if (not e->ExceptionCheck()) {
jclass stringClass = e->FindClass("java/lang/String");
if (not e->ExceptionCheck()) {
jobjectArray a = e->NewObjectArray(ac-1, stringClass, 0);
if (not e->ExceptionCheck()) {
for (int i = 1; i < ac; ++i) {
e->SetObjectArrayElement(a, i-1, e->NewStringUTF(av[i]));
}

e->CallStaticVoidMethod(c, m, a);
}
}
}
}

int exitCode = 0;
if (e->ExceptionCheck()) {
exitCode = -1;
e->ExceptionDescribe();
}

vm->DestroyJavaVM();

return exitCode;
}
EOF
/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/g++ \
-I/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/JavaVM.framework/Headers/ \
-D_JNI_IMPLEMENTATION_ -arch armv6 -isysroot \
/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/ \
-c main.cpp -o main.o
/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/g++ \
-arch armv6 -isysroot \
/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/ \
-rdynamic *.o -ldl -lpthread -lz -framework CoreFoundation \
-Wl,-segprot,__RWX,rwx,rwx -o hello

The result runs on a jailbroken iPhone. I don't have a non-jailbroken
device handy to test, but I'll give it a try if I can get access to one.

It's possible that a stock iPhone won't accept a Mach-O section that's
both writable and executable. If that's the case, it will be a challenge
to support, although it should be possible. I'd prefer not to head down
that road until that theory is confirmed, though. Does anyone happen to
know of any official documentation on this subject?

Joel Dice

unread,
Sep 13, 2011, 11:48:20 AM9/13/11
to Avian
On Tue, 13 Sep 2011, Joel Dice wrote:

> It's possible that a stock iPhone won't accept a Mach-O section that's both
> writable and executable. If that's the case, it will be a challenge to
> support, although it should be possible. I'd prefer not to head down that
> road until that theory is confirmed, though. Does anyone happen to know of
> any official documentation on this subject?

Now that I think about it, this totally makes sense. Since Apple
apparently wants to disallow JIT compilation, they naturally must disallow
writable, executable code segments. Otherwise, I could just make a big,
empty section with those permissions and fill it with JIT-compiled code at
runtime. A bit of googling seems to support this, although I still
haven't seen anything official.

mzechner

unread,
Sep 19, 2011, 6:52:17 PM9/19/11
to Avian
I couldn't see anything official either, but the setiment on the net
seems to be that this is in fact not possible. The reasons you gave
make sense. Is there a way to work-around the need for writeable code
segments? I assume addresses get patched?

Joel Dice

unread,
Sep 19, 2011, 7:51:25 PM9/19/11
to Avian
On Mon, 19 Sep 2011, mzechner wrote:

> I couldn't see anything official either, but the setiment on the net
> seems to be that this is in fact not possible. The reasons you gave
> make sense. Is there a way to work-around the need for writeable code
> segments? I assume addresses get patched?

Yes, there are a couple of ways around it that I can think of; I'm working
on what I think will be the easiest. I hope to have something to show for
it by the end of the week.

>
> On 13 Sep., 08:48, Joel Dice <joel.d...@gmail.com> wrote:
>> On Tue, 13 Sep 2011, Joel Dice wrote:
>>> It's possible that a stock iPhone won't accept a Mach-O section that's both
>>> writable and executable. �If that's the case, it will be a challenge to
>>> support, although it should be possible. �I'd prefer not to head down that
>>> road until that theory is confirmed, though. �Does anyone happen to know of
>>> any official documentation on this subject?
>>
>> Now that I think about it, this totally makes sense. �Since Apple
>> apparently wants to disallow JIT compilation, they naturally must disallow
>> writable, executable code segments. �Otherwise, I could just make a big,
>> empty section with those permissions and fill it with JIT-compiled code at
>> runtime. �A bit of googling seems to support this, although I still
>> haven't seen anything official.
>

Joel Dice

unread,
Sep 22, 2011, 11:54:15 PM9/22/11
to Avian
On Mon, 19 Sep 2011, Joel Dice wrote:

> On Mon, 19 Sep 2011, mzechner wrote:
>
>> I couldn't see anything official either, but the setiment on the net
>> seems to be that this is in fact not possible. The reasons you gave
>> make sense. Is there a way to work-around the need for writeable code
>> segments? I assume addresses get patched?
>
> Yes, there are a couple of ways around it that I can think of; I'm working on
> what I think will be the easiest. I hope to have something to show for it by
> the end of the week.

I finished this work and created a simple iOS app that displays "Hello,
World!" using Java:

git clone git://oss.readytalk.com/hello-ios.git

There's a readme.txt in that project which explains how to build it. The
build process currently requires a command-line step and a step to finish
the build from within the IDE. Someone with better XCode skills than mine
may be able to streamline this so you can build entirely from within the
IDE or entirely from the command line.

mzechner

unread,
Sep 28, 2011, 11:36:39 PM9/28/11
to Avian
This is amazing. I'll put this to work on my end and report back how
things go. Maybe i can at least help streamline the process.

I can't state often enough how much i appreciate the work you put into
this. Keep it up and thank you!

Joel Dice

unread,
Sep 30, 2011, 3:33:56 PM9/30/11
to Avian
OK, my last email was premature (again). There was one more thing that
needed changing to guarantee we don't use any runtime-generated code,
which I've just committed.

I only recently got access to a non-jailbroken device, which is why I
didn't catch this until now. I kind of wish the iOS Simulator was as
restrictive about writable, executable memory as real, non-jailbroken
hardware is; then I would have noticed this sooner.

Joel Dice

unread,
Oct 3, 2011, 5:23:57 PM10/3/11
to Avian
FYI, I've merged the ios branch into master and plan to delete the ios
branch soon. Any further iOS work will go straight into master unless
it's destabilizing enough to warrant its own branch.

Mario Zechner

unread,
Nov 3, 2011, 9:23:11 AM11/3/11
to Avian
I had to temporarily stop working with Avian due to an insane work
load. The last time i tested the iOS branch it indeed failed on non-
jailbroken devices. I'll give it a try asap. Thanks for all your hard
work!

On 3 Okt., 22:23, Joel Dice <joel.d...@gmail.com> wrote:
> FYI, I've merged the ios branch into master and plan to delete the ios
> branch soon.  Any further iOS work will go straight into master unless
> it's destabilizing enough to warrant its own branch.
>
>
>
>
>
>
>
> On Fri, 30 Sep 2011, Joel Dice wrote:
> > OK, my last email was premature (again).  There was one more thing that
> > needed changing to guarantee we don't use any runtime-generated code, which
> > I've just committed.
>
> > I only recently got access to a non-jailbroken device, which is why I didn't
> > catch this until now.  I kind of wish the iOS Simulator was as restrictive
> > about writable, executable memory as real, non-jailbroken hardware is; then I
> > would have noticed this sooner.
>
> > On Wed, 28 Sep 2011, mzechner wrote:
>
> >>  This is amazing. I'll put this to work on my end and report back how
> >>  things go. Maybe i can at least help streamline the process.
>
> >>  I can't state often enough how much i appreciate the work you put into
> >>  this. Keep it up and thank you!
>
> >>  On 22 Sep., 20:54, Joel Dice <joel.d...@gmail.com> wrote:
> >> >  On Mon, 19 Sep 2011, Joel Dice wrote:
> >> > >  On Mon, 19 Sep 2011, mzechner wrote:
>
> >> > > >  ï¿½I couldn't see anything official either, but the setiment on the
> >> > > >  net
> >> > > >  ï¿½seems to be that this is in fact not possible. The reasons you gave
> >> > > >  ï¿½make sense. Is there a way to work-around the need for writeable
> >> > > >  code
> >> > > >  ï¿½segments? I assume addresses get patched?
>
> >> > >  Yes, there are a couple of ways around it that I can think of; I'm
> >> > >  working on
> >> > >  what I think will be the easiest. �I hope to have something to show
> >> > >  for it by
> >> > >  the end of the week.
>
> >> >  I finished this work and created a simple iOS app that displays "Hello,
> >> >  World!" using Java:
>
> >> >  ï¿½ �git clone git://oss.readytalk.com/hello-ios.git
>
> >> >  There's a readme.txt in that project which explains how to build it.
> >> >  ï¿½The
> >> >  build process currently requires a command-line step and a step to
> >> >  finish
> >> >  the build from within the IDE. �Someone with better XCode skills than

Joel Dice

unread,
Jan 7, 2012, 12:29:20 PM1/7/12
to Anton Shevchenko, av...@googlegroups.com
Hi Anton,

On Sat, 7 Jan 2012, Anton Shevchenko wrote:

> I have some difficulties trying to build hello-ios to run on the
> iPhone.
> I have successfully built the hello-ios project for the simulator
> (needed to exclude objective c stuff from the hello.m file as Xcode3
> told about undefined symbols on the linker stage - anyway, this issue
> is not critical).
> So, after getting to run 'Hello world' in the simulator I decided to
> try out to build this sample for the iPhone (not jailbraked).
> When I build avian for the arm architecture I am getting the following
> error
>
> Undefined symbols for architecture armv6:
> "_JNI_CreateJavaVM", referenced from:
> -[helloAppDelegate application:didFinishLaunchingWithOptions:]
> in helloAppDelegate.o
> ld: symbol(s) not found for architecture armv6
>
> But when I am trying to build for the armv6 architecture the tool
> binaryToObject fails with 'unsupported platform'.

Did you follow the directions in readme.txt? Note that you must build
from the command line using the makefile. If you build from within XCode
first without running make, it will fail with the error you encountered.

If you still get that error even after running make from the command line,
please post the complete output of "make clean && make". Thanks.

By the way, if you're using the iOS 5.0 SDK, you'll need to change "4.3"
to "5.0" in the makefile.

>
> I am really wondering I got so far with Avian - thank you a lot, Joel.

Joel Dice

unread,
Jan 7, 2012, 12:33:43 PM1/7/12
to Joel Dice, Anton Shevchenko, av...@googlegroups.com

Also, it is necessary to run "make clean" each time you switch from a
simulator build to a device (i.e. ARM) build and vice-versa.

mzechner

unread,
Jan 7, 2012, 12:58:55 PM1/7/12
to Avian
Oh, sorry, should have followed up on that. Everything went smoothly
without any problems. Thanks a ton!
> > On Oct 4 2011, 1:23�am, Joel Dice <joel.d...@gmail.com> wrote:
> >> FYI, I've merged the ios branch into master and plan to delete the ios
> >> branch soon. �Any further iOS work will go straight into master unless
> >> it's destabilizing enough to warrant its own branch.
>
> >> On Fri, 30 Sep 2011, Joel Dice wrote:
> >>> OK, my last email was premature (again). �There was one more thing that
> >>> needed changing to guarantee we don't use any runtime-generated code, which
> >>> I've just committed.
>
> >>> I only recently got access to a non-jailbroken device, which is why I didn't
> >>> catch this until now. �I kind of wish the iOS Simulator was as restrictive
> >>> about writable, executable memory as real, non-jailbroken hardware is; then I
> >>> would have noticed this sooner.
>
> >>> On Wed, 28 Sep 2011, mzechner wrote:
>
> >>>> �This is amazing. I'll put this to work on my end and report back how
> >>>> �things go. Maybe i can at least help streamline the process.
>
> >>>> �I can't state often enough how much i appreciate the work you put into
> >>>> �this. Keep it up and thank you!
>
> >>>> �On 22 Sep., 20:54, Joel Dice <joel.d...@gmail.com> wrote:
> >>>>> �On Mon, 19 Sep 2011, Joel Dice wrote:
> >>>>>> �On Mon, 19 Sep 2011, mzechner wrote:
>
> >>>>>>> ��I couldn't see anything official either, but the setiment on the
> >>>>>>> �net
> >>>>>>> ��seems to be that this is in fact not possible. The reasons you gave
> >>>>>>> ��make sense. Is there a way to work-around the need for writeable
> >>>>>>> �code
> >>>>>>> ��segments? I assume addresses get patched?
>
> >>>>>> �Yes, there are a couple of ways around it that I can think of; I'm
> >>>>>> �working on
> >>>>>> �what I think will be the easiest. �I hope to have something to show
> >>>>>> �for it by
> >>>>>> �the end of the week.
>
> >>>>> �I finished this work and created a simple iOS app that displays "Hello,
> >>>>> �World!" using Java:
>
> >>>>> �� �git clone git://oss.readytalk.com/hello-ios.git
>
> >>>>> �There's a readme.txt in that project which explains how to build it.
> >>>>> ��The
> >>>>> �build process currently requires a command-line step and a step to
> >>>>> �finish
> >>>>> �the build from within the IDE. �Someone with better XCode skills than
> >>>>> �mine
> >>>>> �may be able to streamline this so you can build entirely from within the
> >>>>> �IDE or entirely from the command line.
>
> >>>> �--
> >>>> �You received this message because you are subscribed to the Google Groups
> >>>> �"Avian" group.
> >>>> �To post to this group, send email to av...@googlegroups.com.
> >>>> �To unsubscribe from this group, send email to
> >>>> �avian+un...@googlegroups.com.
> >>>> �For more options, visit this group at
> >>>> �http://groups.google.com/group/avian?hl=en.

Daniel Jaeger

unread,
Feb 26, 2012, 8:47:10 PM2/26/12
to Avian
Hi Joel,

great work on Avian.
I'm considering Avian as scripting-backend for a game. I've already
played around with it and it seems to work great.
As I understand it's not possible to allocate memory that's both
writeable and executable on iOS devices.
So the current solution is to compile the boot image into an object
file and mark this file executable.
For this process to work we have to use the "bootimage-generator" so
the java byte code can be directly executed without JITing it.

So here are my questions:

- why is a code image even necessary in those cases? Wouldn't the boot
image be enough?
- I've tried to build the hello-world sample with (make run-
proguard=false) but it failed. Why is pro guard required?
- would it be possible to avoid the JIT compilation and revert to a
slow interpreter, similar to Lua, that's safe on the iOS devices?

Best,

Daniel
> >>  On Oct 4 2011, 1:23�am, Joel Dice <joel.d...@gmail.com> wrote:
> >> >  FYI, I've merged the ios branch into master and plan to delete the ios
> >> >  branch soon. �Any further iOS work will go straight into master unless
> >> >  it's destabilizing enough to warrant its own branch.
>
> >> >  On Fri, 30 Sep 2011, Joel Dice wrote:
> >> > >  OK, my last email was premature (again). �There was one more thing
> >> > >  that
> >> > >  needed changing to guarantee we don't use any runtime-generated code,
> >> > >  which
> >> > >  I've just committed.
>
> >> > >  I only recently got access to a non-jailbroken device, which is why I
> >> > >  didn't
> >> > >  catch this until now. �I kind of wish the iOS Simulator was as
> >> > >  restrictive
> >> > >  about writable, executable memory as real, non-jailbroken hardware is;
> >> > >  then I
> >> > >  would have noticed this sooner.
>
> >> > >  On Wed, 28 Sep 2011, mzechner wrote:
>
> >> > > >  ï¿½This is amazing. I'll put this to work on my end and report back
> >> > > >  how
> >> > > >  ï¿½things go. Maybe i can at least help streamline the process.
>
> >> > > >  ï¿½I can't state often enough how much i appreciate the work you put
> >> > > >  into
> >> > > >  ï¿½this. Keep it up and thank you!
>
> >> > > >  ï¿½On 22 Sep., 20:54, Joel Dice <joel.d...@gmail.com> wrote:
> >> > > > >  ï¿½On Mon, 19 Sep 2011, Joel Dice wrote:
> >> > > > > >  ï¿½On Mon, 19 Sep 2011, mzechner wrote:
>
> >> > > > > > >  ï¿½ï¿½I couldn't see anything official either, but the setiment
> >> > > > > > >  on the
> >> > > > > > >  ï¿½net
> >> > > > > > >  ï¿½ï¿½seems to be that this is in fact not possible. The reasons
> >> > > > > > >  you gave
> >> > > > > > >  ï¿½ï¿½make sense. Is there a way to work-around the need for
> >> > > > > > >  writeable
> >> > > > > > >  ï¿½code
> >> > > > > > >  ï¿½ï¿½segments? I assume addresses get patched?
>
> >> > > > > >  ï¿½Yes, there are a couple of ways around it that I can think of;
> >> > > > > >  I'm
> >> > > > > >  ï¿½working on
> >> > > > > >  ï¿½what I think will be the easiest. �I hope to have something
> >> > > > > >  to show
> >> > > > > >  ï¿½for it by
> >> > > > > >  ï¿½the end of the week.
>
> >> > > > >  ï¿½I finished this work and created a simple iOS app that displays
> >> > > > >  "Hello,
> >> > > > >  ï¿½World!" using Java:
>
> >> > > > >  ï¿½ï¿½ �git clone git://oss.readytalk.com/hello-ios.git
>
> >> > > > >  ï¿½There's a readme.txt in that project which explains how to build
> >> > > > >  it.
> >> > > > >  ï¿½ï¿½The
> >> > > > >  ï¿½build process currently requires a command-line step and a step
> >> > > > >  to
> >> > > > >  ï¿½finish
> >> > > > >  ï¿½the build from within the IDE. �Someone with better XCode
> >> > > > >  skills than
> >> > > > >  ï¿½mine
> >> > > > >  ï¿½may be able to streamline this so you can build entirely from
> >> > > > >  within the
> >> > > > >  ï¿½IDE or entirely from the command line.
>
> >> > > >  ï¿½--
> >> > > >  ï¿½You received this message because you are subscribed to the Google
> >> > > >  Groups
> >> > > >  ï¿½"Avian" group.
> >> > > >  ï¿½To post to this group, send email to av...@googlegroups.com.
> >> > > >  ï¿½To unsubscribe from this group, send email to
> >> > > >  ï¿½avian+un...@googlegroups.com.
> >> > > >  ï¿½For more options, visit this group at
> >> > > >  ï¿½http://groups.google.com/group/avian?hl=en.
Message has been deleted

Joel Dice

unread,
Feb 27, 2012, 11:29:10 AM2/27/12
to Avian, dwckon...@googlemail.com
Hi Daniel,

On Sun, 26 Feb 2012, Daniel Jaeger wrote:

> Hi Joel,
>
> great work on Avian.
> I'm considering Avian as scripting-backend for a game. I've already
> played around with it and it seems to work great.
> As I understand it's not possible to allocate memory that's both
> writeable and executable on iOS devices.
> So the current solution is to compile the boot image into an object
> file and mark this file executable.
> For this process to work we have to use the "bootimage-generator" so
> the java byte code can be directly executed without JITing it.

The bootimage-generator does two things: (1) it preparses all the class
files in the application and creates a heap image which contains those
classes in a VM-specific format, minus any bytecode, and (2) it
ahead-of-time compiles all the bytecode to machine code to create a
read-only code image.

> So here are my questions:
>
> - why is a code image even necessary in those cases? Wouldn't the boot
> image be enough?

What I've been calling the boot image is composed of two things: the heap
image and the code image. If you don't want or need AOT compilation and
are willing to accept the performance penalty of interpreted bytecode,
you're better off not doing a boot image build at all and just embedding a
jar file instead.

> - I've tried to build the hello-world sample with (make run-
> proguard=false) but it failed. Why is pro guard required?

Sorry, that was a bug in the makefile. Please pull the latest from the
Git repo and try again.

> - would it be possible to avoid the JIT compilation and revert to a
> slow interpreter, similar to Lua, that's safe on the iOS devices?

Yes, if you need to be able to load bytecode at runtime and you don't care
much about performance, that will work. I'll update the hello-ios example
makefile to provide this option when I have some time.

>>>> �On Oct 4 2011, 1:23�am, Joel Dice <joel.d...@gmail.com> wrote:
>>>>> �FYI, I've merged the ios branch into master and plan to delete the ios

>>>>> �branch soon. �Any further iOS work will go straight into master unless


>>>>> �it's destabilizing enough to warrant its own branch.
>>
>>>>> �On Fri, 30 Sep 2011, Joel Dice wrote:

>>>>>> �OK, my last email was premature (again). �There was one more thing


>>>>>> �that
>>>>>> �needed changing to guarantee we don't use any runtime-generated code,
>>>>>> �which
>>>>>> �I've just committed.
>>
>>>>>> �I only recently got access to a non-jailbroken device, which is why I
>>>>>> �didn't

>>>>>> �catch this until now. �I kind of wish the iOS Simulator was as


>>>>>> �restrictive
>>>>>> �about writable, executable memory as real, non-jailbroken hardware is;
>>>>>> �then I
>>>>>> �would have noticed this sooner.
>>
>>>>>> �On Wed, 28 Sep 2011, mzechner wrote:
>>

>>>>>>> ��This is amazing. I'll put this to work on my end and report back
>>>>>>> �how
>>>>>>> ��things go. Maybe i can at least help streamline the process.
>>
>>>>>>> ��I can't state often enough how much i appreciate the work you put
>>>>>>> �into
>>>>>>> ��this. Keep it up and thank you!
>>
>>>>>>> ��On 22 Sep., 20:54, Joel Dice <joel.d...@gmail.com> wrote:
>>>>>>>> ��On Mon, 19 Sep 2011, Joel Dice wrote:
>>>>>>>>> ��On Mon, 19 Sep 2011, mzechner wrote:
>>
>>>>>>>>>> ���I couldn't see anything official either, but the setiment
>>>>>>>>>> �on the
>>>>>>>>>> ��net
>>>>>>>>>> ���seems to be that this is in fact not possible. The reasons
>>>>>>>>>> �you gave
>>>>>>>>>> ���make sense. Is there a way to work-around the need for
>>>>>>>>>> �writeable
>>>>>>>>>> ��code
>>>>>>>>>> ���segments? I assume addresses get patched?
>>
>>>>>>>>> ��Yes, there are a couple of ways around it that I can think of;
>>>>>>>>> �I'm
>>>>>>>>> ��working on
>>>>>>>>> ��what I think will be the easiest. �I hope to have something
>>>>>>>>> �to show
>>>>>>>>> ��for it by
>>>>>>>>> ��the end of the week.
>>
>>>>>>>> ��I finished this work and created a simple iOS app that displays
>>>>>>>> �"Hello,
>>>>>>>> ��World!" using Java:
>>
>>>>>>>> ��� �git clone git://oss.readytalk.com/hello-ios.git
>>
>>>>>>>> ��There's a readme.txt in that project which explains how to build
>>>>>>>> �it.
>>>>>>>> ���The
>>>>>>>> ��build process currently requires a command-line step and a step
>>>>>>>> �to
>>>>>>>> ��finish
>>>>>>>> ��the build from within the IDE. �Someone with better XCode
>>>>>>>> �skills than
>>>>>>>> ��mine
>>>>>>>> ��may be able to streamline this so you can build entirely from
>>>>>>>> �within the
>>>>>>>> ��IDE or entirely from the command line.
>>
>>>>>>> ��--
>>>>>>> ��You received this message because you are subscribed to the Google
>>>>>>> �Groups
>>>>>>> ��"Avian" group.
>>>>>>> ��To post to this group, send email to av...@googlegroups.com.
>>>>>>> ��To unsubscribe from this group, send email to
>>>>>>> ��avian+un...@googlegroups.com.
>>>>>>> ��For more options, visit this group at
>>>>>>> ��http://groups.google.com/group/avian?hl=en.
>
> --
> You received this message because you are subscribed to the Google Groups "Avian" group.


> To post to this group, send email to av...@googlegroups.com.

> To unsubscribe from this group, send email to avian+un...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/avian?hl=en.
>
>

Daniel Jaeger

unread,
Feb 28, 2012, 3:06:21 PM2/28/12
to Avian
Hi Joel,

thanks for the reply. I'll try the updated hello-iOS example in a few
minutes.

> Yes, if you need to be able to load bytecode at runtime and you don't care
> much about performance, that will work. I'll update the hello-ios example
> makefile to provide this option when I have some time.

That would be amazing. It would be incredibly helpful to be able to
test on the device without having to
precompile to machine code. Changed code could be pulled from a
network server and recompiled on the fly.
Building the boot/code image only for release mode would be the best
possible solution I guess.

Best,

Daniel
> > On Jan 7, 6:33�pm, Joel Dice <joel.d...@gmail.com> wrote:
> >> On Sat, 7 Jan 2012, Joel Dice wrote:
> >>> Hi Anton,
>
> >>> On Sat, 7 Jan 2012, Anton Shevchenko wrote:
>
> >>>> �I have some difficulties trying to build hello-ios to run on the
> >>>> �iPhone.
> >>>> �I have successfully built the hello-ios project for the simulator
> >>>> �(needed to exclude objective c stuff from the hello.m file as Xcode3
> >>>> �told about undefined symbols on the linker stage - anyway, this issue
> >>>> �is not critical).
> >>>> �So, after getting to run 'Hello world' in the simulator I decided to
> >>>> �try out to build this sample for the iPhone (not jailbraked).
> >>>> �When I build avian for the arm architecture I am getting the following
> >>>> �error
>
> >>>> �Undefined symbols for architecture armv6:
> >>>> � "_JNI_CreateJavaVM", referenced from:
> >>>> � � � -[helloAppDelegate application:didFinishLaunchingWithOptions:]
> >>>> �in helloAppDelegate.o
> >>>> �ld: symbol(s) not found for architecture armv6
>
> >>>> �But when I am trying to build for the armv6 architecture the tool
> >>>> �binaryToObject fails with 'unsupported platform'.
>
> >>> Did you follow the directions in readme.txt? �Note that you must build from
> >>> the command line using the makefile. �If you build from within XCode first
> >>> without running make, it will fail with the error you encountered.
>
> >>> If you still get that error even after running make from the command line,
> >>> please post the complete output of "make clean && make". �Thanks.
>
> >>> By the way, if you're using the iOS 5.0 SDK, you'll need to change "4.3" to
> >>> "5.0" in the makefile.
>
> >> Also, it is necessary to run "make clean" each time you switch from a
> >> simulator build to a device (i.e. ARM) build and vice-versa.
>
> >>>> �I am really wondering I got so far with Avian - thank you a lot, Joel.
>
> >>>> �On Oct 4 2011, 1:23�am, Joel Dice <joel.d...@gmail.com> wrote:
> >>>>> �FYI, I've merged the ios branch into master and plan to delete the ios
> >>>>> �branch soon. �Any further iOS work will go straight into master unless
> >>>>> �it's destabilizing enough to warrant its own branch.
>
> >>>>> �On Fri, 30 Sep 2011, Joel Dice wrote:
> >>>>>> �OK, my last email was premature (again). �There was one more thing
> >>>>>> �that
> >>>>>> �needed changing to guarantee we don't use any runtime-generated code,
> >>>>>> �which
> >>>>>> �I've just committed.
>
> >>>>>> �I only recently got access to a non-jailbroken device, which is why I
> >>>>>> �didn't
> >>>>>> �catch this until now. �I kind of wish the iOS Simulator was as
> >>>>>> �restrictive
> >>>>>> �about writable, executable memory as real, non-jailbroken hardware is;
> >>>>>> �then I
> >>>>>> �would have noticed this sooner.
>
> >>>>>> �On Wed, 28 Sep 2011, mzechner wrote:
>
> >>>>>>> ��This is amazing. I'll put this to work on my end and report back
> >>>>>>> �how
> >>>>>>> ��things go. Maybe i can at least help streamline the process.
>
> >>>>>>> ��I can't state often enough how much i appreciate the work you put
> >>>>>>> �into
> >>>>>>> ��this. Keep it up and thank you!
>
> >>>>>>> ��On 22 Sep., 20:54, Joel Dice <joel.d...@gmail.com> wrote:
> >>>>>>>> ��On Mon, 19 Sep 2011, Joel Dice wrote:
> >>>>>>>>> ��On Mon, 19 Sep 2011, mzechner wrote:
>
> >>>>>>>>>> ���I couldn't see anything official either, but the setiment
> >>>>>>>>>> �on the
> >>>>>>>>>> ��net
> >>>>>>>>>> ���seems to be that this is in fact not possible. The reasons
> >>>>>>>>>> �you gave
> >>>>>>>>>> ���make sense. Is there a way to work-around the need for
> >>>>>>>>>> �writeable
> >>>>>>>>>> ��code
> >>>>>>>>>> ���segments? I assume addresses get patched?
>
> >>>>>>>>> ��Yes, there are a couple of ways around it that I can think of;
> >>>>>>>>> �I'm
> >>>>>>>>> ��working on
> >>>>>>>>> ��what I think will be the easiest. �I hope to have something
> >>>>>>>>> �to show
> >>>>>>>>> ��for it by
> >>>>>>>>> ��the end of the week.
>
> >>>>>>>> ��I finished this work and created a simple iOS app that displays
> >>>>>>>> �"Hello,
> >>>>>>>> ��World!" using Java:
>
> >>>>>>>> ��� �git clone git://oss.readytalk.com/hello-ios.git
>
> >>>>>>>> ��There's a readme.txt in that project which explains how to build
> >>>>>>>> �it.
> >>>>>>>> ���The
> >>>>>>>> ��build process currently requires a command-line step and a step
> >>>>>>>> �to
> >>>>>>>> ��finish
> >>>>>>>> ��the build from within the IDE. �Someone with better XCode
> >>>>>>>> �skills than
> >>>>>>>> ��mine
> >>>>>>>> ��may be able to streamline this so you can build entirely from
> >>>>>>>> �within the
> >>>>>>>> ��IDE or entirely from the command line.
>
> >>>>>>> ��--
> >>>>>>> ��You received this message because you are subscribed to the Google
> >>>>>>> �Groups
> >>>>>>> ��"Avian" group.
> >>>>>>> ��To post to this group, send email to av...@googlegroups.com.
> >>>>>>> ��To unsubscribe from this group, send email to
> >>>>>>> ��avian+un...@googlegroups.com.
> >>>>>>> ��For more options, visit this group at
> >>>>>>> ��http://groups.google.com/group/avian?hl=en.

Daniel Jaeger

unread,
Feb 28, 2012, 3:27:30 PM2/28/12
to Avian
Joel,

building for iOS with the new makefile without pro-guard works just
fine!
However I can't seem to be able to pass float values to Java.
I've added the following method to the Hello.java file:

public static void FloatTest(float one, float two, float three)
{
System.out.println("one=" + one + " two=" + two + " three=" +
three);
}

EOF

To invoke the method I added the following code to
"helloAppDelegate.m" line=88

jmethodID floatMethod = (*e)->GetMethodID(e, hello, "FloatTest",
"(FFF)V");

if (! (*e)->ExceptionCheck(e)) {
float one = 123;
float two = 456;
float three = 789;
(*e)->CallStaticVoidMethod(e, hello, floatMethod, one, two ,
three);
}

EOF

However this results in the following output:

one=0 two=3.48047 three=0

The identical code works if doubles are used instead of floats in both
Java and C.


Best,

Daniel

On Feb 28, 9:06 pm, Daniel Jaeger <dwckongreg...@googlemail.com>
wrote:

Joel Dice

unread,
Feb 28, 2012, 4:38:53 PM2/28/12
to Avian
On Tue, 28 Feb 2012, Daniel Jaeger wrote:

> Hi Joel,
>


> thanks for the reply. I'll try the updated hello-iOS example in a few
> minutes.
>
>> Yes, if you need to be able to load bytecode at runtime and you don't care
>> much about performance, that will work. I'll update the hello-ios example
>> makefile to provide this option when I have some time.
>
> That would be amazing. It would be incredibly helpful to be able to
> test on the device without having to
> precompile to machine code. Changed code could be pulled from a
> network server and recompiled on the fly.

If you mean recompiled to machine code, that won't be possible. Or
rather, compiling to machine code at runtime is certainly possible on iOS,
but actually executing it is not, since we aren't able to allocate memory
which is executable. However, if you're talking about recompiling from
source code to byte code on a build server and then loading and running
the bytecode on the fly in interpreted mode, yes, that should work.

As of this change, you can build an interpreted version of hello-ios using
"make process=interpret":

http://oss.readytalk.com/gitweb?p=hello-ios.git;a=commitdiff;h=2aaf6359906ac2dd9fe3daa81fe687b8d15a397d

I'll leave runtime classloading using ClassLoader.defineClass as an
excercise for the reader.

>>> On Jan 7, 6:33�pm, Joel Dice <joel.d...@gmail.com> wrote:
>>>> On Sat, 7 Jan 2012, Joel Dice wrote:
>>>>> Hi Anton,
>>
>>>>> On Sat, 7 Jan 2012, Anton Shevchenko wrote:
>>

>>>>>> �I have some difficulties trying to build hello-ios to run on the
>>>>>> �iPhone.
>>>>>> �I have successfully built the hello-ios project for the simulator
>>>>>> �(needed to exclude objective c stuff from the hello.m file as Xcode3
>>>>>> �told about undefined symbols on the linker stage - anyway, this issue
>>>>>> �is not critical).
>>>>>> �So, after getting to run 'Hello world' in the simulator I decided to
>>>>>> �try out to build this sample for the iPhone (not jailbraked).
>>>>>> �When I build avian for the arm architecture I am getting the following
>>>>>> �error
>>
>>>>>> �Undefined symbols for architecture armv6:
>>>>>> � "_JNI_CreateJavaVM", referenced from:
>>>>>> � � � -[helloAppDelegate application:didFinishLaunchingWithOptions:]
>>>>>> �in helloAppDelegate.o

>>>>>> �ld: symbol(s) not found for architecture armv6
>>
>>>>>> �But when I am trying to build for the armv6 architecture the tool
>>>>>> �binaryToObject fails with 'unsupported platform'.
>>
>>>>> Did you follow the directions in readme.txt? �Note that you must build from
>>>>> the command line using the makefile. �If you build from within XCode first


>>>>> without running make, it will fail with the error you encountered.
>>
>>>>> If you still get that error even after running make from the command line,

>>>>> please post the complete output of "make clean && make". �Thanks.


>>
>>>>> By the way, if you're using the iOS 5.0 SDK, you'll need to change "4.3" to
>>>>> "5.0" in the makefile.
>>
>>>> Also, it is necessary to run "make clean" each time you switch from a
>>>> simulator build to a device (i.e. ARM) build and vice-versa.
>>

>>>>>>>>>> ��may be able to streamline this so you can build entirely from
>>>>>>>>>> �within the
>>>>>>>>>> ��IDE or entirely from the command line.
>>
>>>>>>>>> ��--


>>>>>>>>> ��You received this message because you are subscribed to the Google
>>>>>>>>> �Groups
>>>>>>>>> ��"Avian" group.
>>>>>>>>> ��To post to this group, send email to av...@googlegroups.com.
>>>>>>>>> ��To unsubscribe from this group, send email to
>>>>>>>>> ��avian+un...@googlegroups.com.
>>>>>>>>> ��For more options, visit this group at

>>>>>>>>> ��http://groups.google.com/group/avian?hl=en.

Joel Dice

unread,
Feb 28, 2012, 5:39:16 PM2/28/12
to Avian

Thanks for the report. It should be fixed as of this commit:

http://oss.readytalk.com/gitweb?p=avian.git;a=commitdiff;h=5a5d2a8dd2c69432a4c70e3821931416e53a6b4e

Please let me know if you still have problems.

>>>> On Jan 7, 6:33�pm, Joel Dice <joel.d...@gmail.com> wrote:
>>>>> On Sat, 7 Jan 2012, Joel Dice wrote:
>>>>>> Hi Anton,
>>
>>>>>> On Sat, 7 Jan 2012, Anton Shevchenko wrote:
>>

>>>>>>> �I have some difficulties trying to build hello-ios to run on the
>>>>>>> �iPhone.
>>>>>>> �I have successfully built the hello-ios project for the simulator
>>>>>>> �(needed to exclude objective c stuff from the hello.m file as Xcode3
>>>>>>> �told about undefined symbols on the linker stage - anyway, this issue
>>>>>>> �is not critical).
>>>>>>> �So, after getting to run 'Hello world' in the simulator I decided to
>>>>>>> �try out to build this sample for the iPhone (not jailbraked).
>>>>>>> �When I build avian for the arm architecture I am getting the following
>>>>>>> �error
>>
>>>>>>> �Undefined symbols for architecture armv6:
>>>>>>> � "_JNI_CreateJavaVM", referenced from:
>>>>>>> � � � -[helloAppDelegate application:didFinishLaunchingWithOptions:]
>>>>>>> �in helloAppDelegate.o

>>>>>>> �ld: symbol(s) not found for architecture armv6
>>
>>>>>>> �But when I am trying to build for the armv6 architecture the tool
>>>>>>> �binaryToObject fails with 'unsupported platform'.
>>
>>>>>> Did you follow the directions in readme.txt? �Note that you must build from
>>>>>> the command line using the makefile. �If you build from within XCode first


>>>>>> without running make, it will fail with the error you encountered.
>>
>>>>>> If you still get that error even after running make from the command line,

>>>>>> please post the complete output of "make clean && make". �Thanks.


>>
>>>>>> By the way, if you're using the iOS 5.0 SDK, you'll need to change "4.3" to
>>>>>> "5.0" in the makefile.
>>
>>>>> Also, it is necessary to run "make clean" each time you switch from a
>>>>> simulator build to a device (i.e. ARM) build and vice-versa.
>>

>>>>>>>>>>> ��may be able to streamline this so you can build entirely from
>>>>>>>>>>> �within the
>>>>>>>>>>> ��IDE or entirely from the command line.
>>
>>>>>>>>>> ��--


>>>>>>>>>> ��You received this message because you are subscribed to the Google
>>>>>>>>>> �Groups
>>>>>>>>>> ��"Avian" group.
>>>>>>>>>> ��To post to this group, send email to av...@googlegroups.com.
>>>>>>>>>> ��To unsubscribe from this group, send email to
>>>>>>>>>> ��avian+un...@googlegroups.com.
>>>>>>>>>> ��For more options, visit this group at

>>>>>>>>>> ��http://groups.google.com/group/avian?hl=en.

Daniel Jaeger

unread,
Feb 28, 2012, 6:46:48 PM2/28/12
to Avian
Joel,

super! That fixed the problem.

> However, if you're talking about recompiling from
> source code to byte code on a build server and then loading and running
> the bytecode on the fly in interpreted mode, yes, that should work.

Yes, that's exactly what I was talking about. I'll try the updated
hello-project in a minute.

I came across another bug. envTable->DeleteWeakGlobalRef is not
initialized in jnienv.cpp.
I also noticed that NewWeakGlobalRef is simply pointing to
NewGlobalRef.
Is there any reason you did not implement the WeakRef methods. I'm not
a Java expert, so I don't know
if it's OK to not implement these methods. Maybe the official SDK
actually implements them as stubs.
However, I can't simply use NewGlobalRef for C++ objects as the GC
won't be able to collect these retained objects,
where a Java object is the owner of a C++ instance.
For these cases I would have used NewWeakGlobalRef.
Right now I'm not retaining the jobjects in question, but according to
the JNI-SDK these references can simply change.

~Thanks
> http://oss.readytalk.com/gitweb?p=avian.git;a=commitdiff;h=5a5d2a8dd2...
>
> Please let me know if you still have problems.
>
>
>
>
>
>
>
>
>
> > Best,
>
> > Daniel
>
> > On Feb 28, 9:06�pm, Daniel Jaeger <dwckongreg...@googlemail.com>
> > wrote:
> >> Hi Joel,
>
> >> thanks for the reply. I'll try the updated hello-iOS example in a few
> >> minutes.
>
> >>> Yes, if you need to be able to load bytecode at runtime and you don't care
> >>> much about performance, that will work. �I'll update the hello-ios example
> >>> makefile to provide this option when I have some time.
>
> >> That would be amazing. It would be incredibly helpful to be able to
> >> test on the device without having to
> >> precompile to machine code. Changed code could be pulled from a
> >> network server and recompiled on the fly.
> >> Building the boot/code image only for release mode would be the best
> >> possible solution I guess.
>
> >> Best,
>
> >> Daniel
>
> >> On Feb 27, 5:29�pm, Joel Dice <joel.d...@gmail.com> wrote:
>
> >>> Hi Daniel,
>
> >>> On Sun, 26 Feb 2012, Daniel Jaeger wrote:
> >>>> Hi Joel,
>
> >>>> great work on Avian.
> >>>> I'm considering Avian as scripting-backend for a game. I've already
> >>>> played around with it and it seems to work great.
> >>>> As I understand it's not possible to allocate memory that's both
> >>>> writeable and executable on iOS devices.
> >>>> So the current solution is to compile the boot image into an object
> >>>> file and mark this file executable.
> >>>> For this process to work we have to use the "bootimage-generator" so
> >>>> the java byte code can be directly executed without JITing it.
>
> >>> The bootimage-generator does two things: (1) it preparses all the class
> >>> files in the application and creates a heap image which contains those
> >>> classes in a VM-specific format, minus any bytecode, and (2) it
> >>> ahead-of-time compiles all the bytecode to machine code to create a
> >>> read-only code image.
>
> >>>> So here are my questions:
>
> >>>> - why is a code image even necessary in those cases? Wouldn't the boot
> >>>> image be enough?
>
> >>> What I've been calling the boot image is composed of two things: the heap
> >>> image and the code image. �If you don't want or need AOT compilation and
> >>> are willing to accept the performance penalty of interpreted bytecode,
> >>> you're better off not doing a boot image build at all and just embedding a
> >>> jar file instead.
>
> >>>> - I've tried to build the hello-world sample with (make run-
> >>>> proguard=false) but it failed. Why is pro guard required?
>
> >>> Sorry, that was a bug in the makefile. �Please pull the latest from the
> >>> Git repo and try again.
>
> >>>> - would it be possible to avoid the JIT compilation and revert to a
> >>>> slow interpreter, similar to Lua, that's safe on the iOS devices?
>
> >>> Yes, if you need to be able to load bytecode at runtime and you don't care
> >>> much about performance, that will work. �I'll update the hello-ios example
> >>> makefile to provide this option when I have some time.
>
> >>>> Best,
>
> >>>> Daniel
>
> >>>> On Jan 7, 6:33�pm, Joel Dice <joel.d...@gmail.com> wrote:
> >>>>> On Sat, 7 Jan 2012, Joel Dice wrote:
> >>>>>> Hi Anton,
>
> >>>>>> On Sat, 7 Jan 2012, Anton Shevchenko wrote:
>
> >>>>>>> �I have some difficulties trying to build hello-ios to run on the
> >>>>>>> �iPhone.
> >>>>>>> �I have successfully built the hello-ios project for the simulator
> >>>>>>> �(needed to exclude objective c stuff from the hello.m file as Xcode3
> >>>>>>> �told about undefined symbols on the linker stage - anyway, this issue
> >>>>>>> �is not critical).
> >>>>>>> �So, after getting to run 'Hello world' in the simulator I decided to
> >>>>>>> �try out to build this sample for the iPhone (not jailbraked).
> >>>>>>> �When I build avian for the arm architecture I am getting the following
> >>>>>>> �error
>
> >>>>>>> �Undefined symbols for architecture armv6:
> >>>>>>> � "_JNI_CreateJavaVM", referenced from:
> >>>>>>> � � � -[helloAppDelegate application:didFinishLaunchingWithOptions:]
> >>>>>>> �in helloAppDelegate.o
> >>>>>>> �ld: symbol(s) not found for architecture armv6
>
> >>>>>>> �But when I am trying to build for the armv6 architecture the tool
> >>>>>>> �binaryToObject fails with 'unsupported platform'.
>
> >>>>>> Did you follow the directions in readme.txt? �Note that you must build from
> >>>>>> the command line using the makefile. �If you build from within XCode first
> >>>>>> without running make, it will fail with the error you encountered.
>
> >>>>>> If you still get that error even after running make from the command line,
> >>>>>> please post the complete output of "make clean && make". �Thanks.
>
> >>>>>> By the way, if you're using the iOS 5.0 SDK, you'll need to change "4.3" to
> >>>>>> "5.0" in the makefile.
>
> >>>>> Also, it is necessary to run "make clean" each time you switch from a
> >>>>> simulator build to a device (i.e. ARM) build and vice-versa.
>
> >>>>>>>>>> ��For more options, visit this group at
>
> ...
>
> read more »

Joel Dice

unread,
Feb 28, 2012, 6:57:18 PM2/28/12
to Avian
On Tue, 28 Feb 2012, Daniel Jaeger wrote:

> Joel,
>


> super! That fixed the problem.
>
>> However, if you're talking about recompiling from
>> source code to byte code on a build server and then loading and running
>> the bytecode on the fly in interpreted mode, yes, that should work.
>
> Yes, that's exactly what I was talking about. I'll try the updated
> hello-project in a minute.
>
> I came across another bug. envTable->DeleteWeakGlobalRef is not
> initialized in jnienv.cpp.
> I also noticed that NewWeakGlobalRef is simply pointing to
> NewGlobalRef.
> Is there any reason you did not implement the WeakRef methods. I'm not
> a Java expert, so I don't know
> if it's OK to not implement these methods. Maybe the official SDK
> actually implements them as stubs.

I doubt it. If the VM claims to support a JNI version greater than or
equal to whichever one introduced {New|Delete}WeakGlobalRef, it should
implement them properly. However, our process thus far has been to only
implement stuff as necessary to get a given app working. That way we have
a real-world test case to help make sure we get it right (or at least as
right as that particular use of the feature needs it to be). I guess
we've just never needed DeleteWeakGlobalRef, and I don't remember why
NewWeakGlobalRef was set to the NewGlobalRef.

Anyway, I'll look at what it takes to implement them properly.

>>> On Feb 28, 9:06�pm, Daniel Jaeger <dwckongreg...@googlemail.com>


>>> wrote:
>>>> Hi Joel,
>>
>>>> thanks for the reply. I'll try the updated hello-iOS example in a few
>>>> minutes.
>>
>>>>> Yes, if you need to be able to load bytecode at runtime and you don't care

>>>>> much about performance, that will work. �I'll update the hello-ios example


>>>>> makefile to provide this option when I have some time.
>>
>>>> That would be amazing. It would be incredibly helpful to be able to
>>>> test on the device without having to
>>>> precompile to machine code. Changed code could be pulled from a
>>>> network server and recompiled on the fly.
>>>> Building the boot/code image only for release mode would be the best
>>>> possible solution I guess.
>>
>>>> Best,
>>
>>>> Daniel
>>

>>>> On Feb 27, 5:29�pm, Joel Dice <joel.d...@gmail.com> wrote:
>>
>>>>> Hi Daniel,
>>
>>>>> On Sun, 26 Feb 2012, Daniel Jaeger wrote:
>>>>>> Hi Joel,
>>
>>>>>> great work on Avian.
>>>>>> I'm considering Avian as scripting-backend for a game. I've already
>>>>>> played around with it and it seems to work great.
>>>>>> As I understand it's not possible to allocate memory that's both
>>>>>> writeable and executable on iOS devices.
>>>>>> So the current solution is to compile the boot image into an object
>>>>>> file and mark this file executable.
>>>>>> For this process to work we have to use the "bootimage-generator" so
>>>>>> the java byte code can be directly executed without JITing it.
>>
>>>>> The bootimage-generator does two things: (1) it preparses all the class
>>>>> files in the application and creates a heap image which contains those
>>>>> classes in a VM-specific format, minus any bytecode, and (2) it
>>>>> ahead-of-time compiles all the bytecode to machine code to create a
>>>>> read-only code image.
>>
>>>>>> So here are my questions:
>>
>>>>>> - why is a code image even necessary in those cases? Wouldn't the boot
>>>>>> image be enough?
>>
>>>>> What I've been calling the boot image is composed of two things: the heap

>>>>> image and the code image. �If you don't want or need AOT compilation and


>>>>> are willing to accept the performance penalty of interpreted bytecode,
>>>>> you're better off not doing a boot image build at all and just embedding a
>>>>> jar file instead.
>>
>>>>>> - I've tried to build the hello-world sample with (make run-
>>>>>> proguard=false) but it failed. Why is pro guard required?
>>

>>>>> Sorry, that was a bug in the makefile. �Please pull the latest from the


>>>>> Git repo and try again.
>>
>>>>>> - would it be possible to avoid the JIT compilation and revert to a
>>>>>> slow interpreter, similar to Lua, that's safe on the iOS devices?
>>
>>>>> Yes, if you need to be able to load bytecode at runtime and you don't care

>>>>> much about performance, that will work. �I'll update the hello-ios example


>>>>> makefile to provide this option when I have some time.
>>
>>>>>> Best,
>>
>>>>>> Daniel
>>

>>>>>> On Jan 7, 6:33�pm, Joel Dice <joel.d...@gmail.com> wrote:
>>>>>>> On Sat, 7 Jan 2012, Joel Dice wrote:
>>>>>>>> Hi Anton,
>>
>>>>>>>> On Sat, 7 Jan 2012, Anton Shevchenko wrote:
>>

>>>>>>>>> �I have some difficulties trying to build hello-ios to run on the
>>>>>>>>> �iPhone.
>>>>>>>>> �I have successfully built the hello-ios project for the simulator
>>>>>>>>> �(needed to exclude objective c stuff from the hello.m file as Xcode3
>>>>>>>>> �told about undefined symbols on the linker stage - anyway, this issue
>>>>>>>>> �is not critical).
>>>>>>>>> �So, after getting to run 'Hello world' in the simulator I decided to
>>>>>>>>> �try out to build this sample for the iPhone (not jailbraked).
>>>>>>>>> �When I build avian for the arm architecture I am getting the following
>>>>>>>>> �error
>>
>>>>>>>>> �Undefined symbols for architecture armv6:
>>>>>>>>> � "_JNI_CreateJavaVM", referenced from:
>>>>>>>>> � � � -[helloAppDelegate application:didFinishLaunchingWithOptions:]
>>>>>>>>> �in helloAppDelegate.o

>>>>>>>>> �ld: symbol(s) not found for architecture armv6
>>
>>>>>>>>> �But when I am trying to build for the armv6 architecture the tool
>>>>>>>>> �binaryToObject fails with 'unsupported platform'.
>>
>>>>>>>> Did you follow the directions in readme.txt? �Note that you must build from
>>>>>>>> the command line using the makefile. �If you build from within XCode first


>>>>>>>> without running make, it will fail with the error you encountered.
>>
>>>>>>>> If you still get that error even after running make from the command line,

>>>>>>>> please post the complete output of "make clean && make". �Thanks.


>>
>>>>>>>> By the way, if you're using the iOS 5.0 SDK, you'll need to change "4.3" to
>>>>>>>> "5.0" in the makefile.
>>
>>>>>>> Also, it is necessary to run "make clean" each time you switch from a
>>>>>>> simulator build to a device (i.e. ARM) build and vice-versa.
>>

>>>>>>>>>>>>> ��may be able to streamline this so you can build entirely from
>>>>>>>>>>>>> �within the
>>>>>>>>>>>>> ��IDE or entirely from the command line.
>>
>>>>>>>>>>>> ��--
>>>>>>>>>>>> ��You received this message because you are subscribed to the Google
>>>>>>>>>>>> �Groups
>>>>>>>>>>>> ��"Avian" group.
>>>>>>>>>>>> ��To post to this group, send email to av...@googlegroups.com.
>>>>>>>>>>>> ��To unsubscribe from this group, send email to
>>>>>>>>>>>> ��avian+un...@googlegroups.com.
>>>>>>>>>>>> ��For more options, visit this group at
>>
>> ...
>>
>> read more �
>
> --
> You received this message because you are subscribed to the Google Groups "Avian" group.


> To post to this group, send email to av...@googlegroups.com.

> To unsubscribe from this group, send email to avian+un...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/avian?hl=en.
>
>

Joel Dice

unread,
Feb 28, 2012, 7:51:47 PM2/28/12
to Joel Dice, Avian
On Tue, 28 Feb 2012, Joel Dice wrote:

> On Tue, 28 Feb 2012, Daniel Jaeger wrote:
>
>> Joel,
>>

>> super! That fixed the problem.
>>
>> > However, if you're talking about recompiling from
>> > source code to byte code on a build server and then loading and running
>> > the bytecode on the fly in interpreted mode, yes, that should work.
>>
>> Yes, that's exactly what I was talking about. I'll try the updated
>> hello-project in a minute.
>>
>> I came across another bug. envTable->DeleteWeakGlobalRef is not
>> initialized in jnienv.cpp.
>> I also noticed that NewWeakGlobalRef is simply pointing to
>> NewGlobalRef.
>> Is there any reason you did not implement the WeakRef methods. I'm not
>> a Java expert, so I don't know
>> if it's OK to not implement these methods. Maybe the official SDK
>> actually implements them as stubs.
>
> I doubt it. If the VM claims to support a JNI version greater than or equal
> to whichever one introduced {New|Delete}WeakGlobalRef, it should implement
> them properly. However, our process thus far has been to only implement
> stuff as necessary to get a given app working. That way we have a real-world
> test case to help make sure we get it right (or at least as right as that
> particular use of the feature needs it to be). I guess we've just never
> needed DeleteWeakGlobalRef, and I don't remember why NewWeakGlobalRef was set
> to the NewGlobalRef.
>
> Anyway, I'll look at what it takes to implement them properly.

I've attached a patch which implements them. Please let me know if it
works for you and I'll commit it.

Reading up on the semantics of these methods at
http://java.sun.com/docs/books/jni/, I feel like the design is inherently
dangerous. For example, consider:

jobject weak = NewWeakGlobalRef(env, foo);

...

if (! (*env)->IsSameObject(env, weak, NULL)) {
// DANGER: if a GC occurs after the call to IsSameObject above and
// before the call to CallVoidMethod below, "weak" may be nulled
// out! Since JNI makes no guarantee about what happens when null
// references are dereferenced
// (http://java.sun.com/docs/books/jni/html/pitfalls.html#11223),
// this could lead to a crash or something similarly unpleasant.
(*env)->CallVoidMethod(env, weak, someMethodID);
}

Is there something I'm missing here, or is this really just a
fundamentally unsafe API?

weak-global-refs.patch

Joel Dice

unread,
Feb 28, 2012, 8:02:39 PM2/28/12
to Joel Dice, Avian

Replying to my reply to myself: yes, the above example is dangerous and
can potentially crash any VM, as explained here:

http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html#weak

The only way to make the above example safe is something like this:

jobject weak = NewWeakGlobalRef(env, foo);

...

jobject strong = NewGlobalRef(env, weak);
if (strong) {
(*env)->CallVoidMethod(env, strong, someMethodID);
(*env)->DeleteGlobalRef(env, strong);

Daniel Jaeger

unread,
Feb 28, 2012, 8:21:57 PM2/28/12
to Avian
Joel,

that was fast again. Excellent.

Yes, this is dangerous. But consider the case where the VM creates a
jobject.
The object's constructor invokes a native method that creates the
native class-instance that belongs to the jobject.
The native class also wants to hold a permanently valid reference to
the jobject in the VM and vice versa.
But the reference should not stop the VM from garbage collecting the
object instance. Thus we need a weak global reference and not a strong
global reference.
If the object is being deleted by the VM, the VM will also invoke the
finalizer and kill the native object (if the user didn't do it
manually earlier).

...

I guess even in the example you have posted above it could lead to a
crash. If the object dies in-between the weak and strong reference
creations.
The only solid situation is where the jobject owns the object that
holds the weak reference : and both die together.

I haven't had time to see if the interpreter run works on the iOS
devices. Do you pass special preprocessor flags to GCC when building
an interpreter build or is this something that can be changed during
runtime? Your related commit didn't seem to change much at all,
besides specifying the bootclasspath jar.

Your patch seemed to work great! Thanks.

Daniel
>    http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.htm...
>
> The only way to make the above example safe is something like this:
>
>    jobject weak = NewWeakGlobalRef(env, foo);
>
>    ...
>
>    jobject strong = NewGlobalRef(env, weak);
>    if (strong) {
>      (*env)->CallVoidMethod(env,     strong, someMethodID);
>      (*env)->DeleteGlobalRef(env, strong);
>    }
>
>
>
>
>
>
>
>
>
> >> >   However, I can't simply use NewGlobalRef for C++ objects as the GC
> >> >   won't be able to collect these retained objects,
> >> >   where a Java object is the owner of a C++ instance.
> >> >   For these cases I would have used NewWeakGlobalRef.
> >> >   Right now I'm not retaining the jobjects in question, but according to
> >> >   the JNI-SDK these references can simply change.
>
> >> > ~  Thanks
>
> >> >   On Feb 28, 11:39�pm, Joel Dice <joel.d...@gmail.com> wrote:
> >> > >   On Tue, 28 Feb 2012, Daniel Jaeger wrote:
> >> > > >   Joel,
>
> >> > > >   building for iOS with the new makefile without pro-guard works just
> >> > > >   fine!
> >> > > >   However I can't seem to be able to pass float values to Java.
> >> > > >   I've added the following method to the Hello.java file:
>
> >> > > >   � �public static void FloatTest(float one, float two, float three)
> >> > > >   � �{
> >> > > >   � � � � � �System.out.println("one=" + one + " two=" + two + "
> >> > > >   three=" +
> >> > > >   three);
> >> > > >   � �}
>
> >> > > >   EOF
>
> >> > > >   To invoke the method I added the following code to
> >> > > >   "helloAppDelegate.m" line=88
>
> >> > > >   � � �jmethodID floatMethod = (*e)->GetMethodID(e, hello,
> >> > > >   "FloatTest",
> >> > > >   "(FFF)V");
>
> >> > > >   � � �if (! (*e)->ExceptionCheck(e)) {
> >> > > >   � � � � � � �float one = 123;
> >> > > >   � � � � � � �float two = 456;
> >> > > >   � � � � � � �float three = 789;
> >> > > >   � � � � � � �(*e)->CallStaticVoidMethod(e, hello, floatMethod, one,
> >> > > >   two ,
> >> > > >   three);
> >> > > >   � � �}
>
> >> > > >   EOF
>
> >> > > >   However this results in the following output:
>
> >> > > >   one=0 two=3.48047 three=0
>
> >> > > >   The identical code works if doubles are used instead of floats in
> >> > > >   both
> >> > > >   Java and C.
>
> >> > >   Thanks for the report. �It should be fixed as of this commit:
>
> >> > >  http://oss.readytalk.com/gitweb?p=avian.git;a=commitdiff;h=5a5d2a8dd2...
>
> >> > >   Please let me know if you still have problems.
>
> >> > > >   Best,
>
> >> > > >   Daniel
>
> >> > > >   On Feb 28, 9:06�pm, Daniel Jaeger <dwckongreg...@googlemail.com>
> >> > > >   wrote:
> >> > > > >   Hi Joel,
>
> >> > > > >   thanks for the reply. I'll try the updated hello-iOS example in a
> >> > > > >   few
> >> > > > >   minutes.
>
> >> > > > > >   Yes, if you need to be able to load bytecode at runtime and you
> >> > > > > >   don't care
> >> > > > > >   much about performance, that will work. �I'll update the
> >> > > > > >   hello-ios example
> >> > > > > >   makefile to provide this option when I have some time.
>
> >> > > > >   That would be amazing. It would be incredibly helpful to be able
> >> > > > >   to
> >> > > > >   test on the device without having to
> >> > > > >   precompile to machine code. Changed code could be pulled from a
> >> > > > >   network server and recompiled on the fly.
> >> > > > >   Building the boot/code image only for release mode would be the
> >> > > > >   best
> >> > > > >   possible solution I guess.
>
> >> > > > >   Best,
>
> >> > > > >   Daniel
>
> >> > > > > >   image and the code image. �If you don't want or need AOT
> >> > > > > >   compilation and
> >> > > > > >   are willing to accept the performance penalty of interpreted
> >> > > > > >   bytecode,
> >> > > > > >   you're better off not doing a boot image build at all and just
> >> > > > > >   embedding a
> >> > > > > >   jar file instead.
>
> >> > > > > > >   - I've tried to build the hello-world sample with (make run-
> >> > > > > > >   proguard=false) but it failed. Why is pro guard required?
>
> >> > > > > >   Sorry, that was a bug in the makefile. �Please pull the
> >> > > > > >   latest from the
> >> > > > > >   Git repo and try again.
>
> >> > > > > > >   - would it be possible to avoid the JIT compilation and
> >> > > > > > >   revert to a
> >> > > > > > >   slow interpreter, similar to Lua, that's safe on the iOS
> >> > > > > > >   devices?
>
> >> > > > > >   Yes, if you need to be able to load bytecode at runtime and you
> >> > > > > >   don't care
> >> > > > > >   much about performance, that will work. �I'll update the
> >> > > > > >   hello-ios example
> >> > > > > >   makefile to provide this option when I have some time.
>
> >> > > > > > >   Best,
>
> >> > > > > > >   Daniel
>
> >> > > > > > >   On Jan 7, 6:33�pm, Joel Dice <joel.d...@gmail.com> wrote:
> >> > > > > > > >   On Sat, 7 Jan 2012, Joel Dice wrote:
> >> > > > > > > > >   Hi Anton,
>
> >> > > > > > > > >   On Sat, 7 Jan 2012, Anton Shevchenko wrote:
>
> >> > > > > > > > > >   �I have some difficulties trying to build hello-ios
> >> > > > > > > > > >   to run on the
> >> > > > > > > > > >   �iPhone.
> >> > > > > > > > > >   �I have successfully built the hello-ios project for
> >> > > > > > > > > >   the simulator
>
> ...
>
> read more »

Joel Dice

unread,
Feb 28, 2012, 9:22:39 PM2/28/12
to Avian
On Tue, 28 Feb 2012, Daniel Jaeger wrote:

> Joel,
>


> that was fast again. Excellent.
>
> Yes, this is dangerous. But consider the case where the VM creates a
> jobject.
> The object's constructor invokes a native method that creates the
> native class-instance that belongs to the jobject.
> The native class also wants to hold a permanently valid reference to
> the jobject in the VM and vice versa.
> But the reference should not stop the VM from garbage collecting the
> object instance. Thus we need a weak global reference and not a strong
> global reference.
> If the object is being deleted by the VM, the VM will also invoke the
> finalizer and kill the native object (if the user didn't do it
> manually earlier).
>
> ...
>
> I guess even in the example you have posted above it could lead to a
> crash. If the object dies in-between the weak and strong reference
> creations.

If that happens, the NewGlobalRef call to create the strong reference will
return null, in which case we won't try to dereference it. Unless I'm
missing something, there's no possibility of a crash at any point (unless
there's a bug in the VM, of course).

> The only solid situation is where the jobject owns the object that
> holds the weak reference : and both die together.
>
> I haven't had time to see if the interpreter run works on the iOS
> devices. Do you pass special preprocessor flags to GCC when building
> an interpreter build or is this something that can be changed during
> runtime? Your related commit didn't seem to change much at all,
> besides specifying the bootclasspath jar.

If you specify a process=interpret option to make, the -DBOOT_IMAGE
preprocessor flag will *not* be passed to GCC, which means boot.c will
define a bootJar function instead of the bootimageBin and codeimageBin
functions defined for the bootimage build. When the VM is initialized, it
will first try to resolve the bootimageBin and codeimageBin functions, but
that will fail in the interpreted build, so it will fall back to a normal
initialization process, using the bootJar function to get access to the
embedded JAR file containing both the application classes and the standard
library classes.

If you're asking whether it's possible to build Avian such that a boot
image is used for all the classes known at build time, but still allow the
interpreter to load classes at runtime, no, that's not currently possible.
We can do it on platforms where JIT compilation is available (i.e.
everywhere but iOS), but the non-JIT interpreter is not currently
compatible with boot image builds. Making it compatible would require
some refactoring, which I'd be willing to pursue if there's enough
interest in it.

>>>>> � On Feb 28, 11:39�pm, Joel Dice <joel.d...@gmail.com> wrote:
>>>>>> � On Tue, 28 Feb 2012, Daniel Jaeger wrote:
>>>>>>> � Joel,
>>
>>>>>>> � building for iOS with the new makefile without pro-guard works just
>>>>>>> � fine!
>>>>>>> � However I can't seem to be able to pass float values to Java.
>>>>>>> � I've added the following method to the Hello.java file:
>>

>>>>>>> � � �public static void FloatTest(float one, float two, float three)
>>>>>>> � � �{
>>>>>>> � � � � � � �System.out.println("one=" + one + " two=" + two + "
>>>>>>> � three=" +
>>>>>>> � three);
>>>>>>> � � �}
>>
>>>>>>> � EOF


>>
>>>>>>> � To invoke the method I added the following code to
>>>>>>> � "helloAppDelegate.m" line=88
>>

>>>>>>> � � � �jmethodID floatMethod = (*e)->GetMethodID(e, hello,
>>>>>>> � "FloatTest",
>>>>>>> � "(FFF)V");
>>
>>>>>>> � � � �if (! (*e)->ExceptionCheck(e)) {


>>>>>>> � � � � � � � �float one = 123;
>>>>>>> � � � � � � � �float two = 456;
>>>>>>> � � � � � � � �float three = 789;

>>>>>>> � � � � � � � �(*e)->CallStaticVoidMethod(e, hello, floatMethod, one,
>>>>>>> � two ,
>>>>>>> � three);
>>>>>>> � � � �}
>>
>>>>>>> � EOF


>>
>>>>>>> � However this results in the following output:
>>
>>>>>>> � one=0 two=3.48047 three=0
>>
>>>>>>> � The identical code works if doubles are used instead of floats in
>>>>>>> � both
>>>>>>> � Java and C.
>>

>>>>>> � Thanks for the report. �It should be fixed as of this commit:


>>
>>>>>> �http://oss.readytalk.com/gitweb?p=avian.git;a=commitdiff;h=5a5d2a8dd2...
>>
>>>>>> � Please let me know if you still have problems.
>>
>>>>>>> � Best,
>>
>>>>>>> � Daniel
>>

>>>>>>> � On Feb 28, 9:06�pm, Daniel Jaeger <dwckongreg...@googlemail.com>


>>>>>>> � wrote:
>>>>>>>> � Hi Joel,
>>
>>>>>>>> � thanks for the reply. I'll try the updated hello-iOS example in a
>>>>>>>> � few
>>>>>>>> � minutes.
>>
>>>>>>>>> � Yes, if you need to be able to load bytecode at runtime and you
>>>>>>>>> � don't care

>>>>>>>>> � much about performance, that will work. �I'll update the


>>>>>>>>> � hello-ios example
>>>>>>>>> � makefile to provide this option when I have some time.
>>
>>>>>>>> � That would be amazing. It would be incredibly helpful to be able
>>>>>>>> � to
>>>>>>>> � test on the device without having to
>>>>>>>> � precompile to machine code. Changed code could be pulled from a
>>>>>>>> � network server and recompiled on the fly.
>>>>>>>> � Building the boot/code image only for release mode would be the
>>>>>>>> � best
>>>>>>>> � possible solution I guess.
>>
>>>>>>>> � Best,
>>
>>>>>>>> � Daniel
>>

>>>>>>>>> � image and the code image. �If you don't want or need AOT


>>>>>>>>> � compilation and
>>>>>>>>> � are willing to accept the performance penalty of interpreted
>>>>>>>>> � bytecode,
>>>>>>>>> � you're better off not doing a boot image build at all and just
>>>>>>>>> � embedding a
>>>>>>>>> � jar file instead.
>>
>>>>>>>>>> � - I've tried to build the hello-world sample with (make run-
>>>>>>>>>> � proguard=false) but it failed. Why is pro guard required?
>>

>>>>>>>>> � Sorry, that was a bug in the makefile. �Please pull the


>>>>>>>>> � latest from the
>>>>>>>>> � Git repo and try again.
>>
>>>>>>>>>> � - would it be possible to avoid the JIT compilation and
>>>>>>>>>> � revert to a
>>>>>>>>>> � slow interpreter, similar to Lua, that's safe on the iOS
>>>>>>>>>> � devices?
>>
>>>>>>>>> � Yes, if you need to be able to load bytecode at runtime and you
>>>>>>>>> � don't care

>>>>>>>>> � much about performance, that will work. �I'll update the


>>>>>>>>> � hello-ios example
>>>>>>>>> � makefile to provide this option when I have some time.
>>
>>>>>>>>>> � Best,
>>
>>>>>>>>>> � Daniel
>>

>>>>>>>>>> � On Jan 7, 6:33�pm, Joel Dice <joel.d...@gmail.com> wrote:
>>>>>>>>>>> � On Sat, 7 Jan 2012, Joel Dice wrote:
>>>>>>>>>>>> � Hi Anton,
>>
>>>>>>>>>>>> � On Sat, 7 Jan 2012, Anton Shevchenko wrote:
>>

>>>>>>>>>>>>> � �I have some difficulties trying to build hello-ios
>>>>>>>>>>>>> � to run on the
>>>>>>>>>>>>> � �iPhone.
>>>>>>>>>>>>> � �I have successfully built the hello-ios project for
>>>>>>>>>>>>> � the simulator
>>
>> ...
>>

Daniel Jaeger

unread,
Feb 29, 2012, 7:52:35 AM2/29/12
to Avian
Joel,

ah. I didn't notice that the interpreter was already available, don't
know how I could miss that.
I've directly setup my own project files to build the VM with the
compiler included.
I just added interpret.cpp and removed architecture specific files and
it works perfectly.

> If you're asking whether it's possible to build Avian such that a boot
> image is used for all the classes known at build time, but still allow the
> interpreter to load classes at runtime, no, that's not currently possible.
The boot image compiled as machine code, and additional classes in the
class path executed with the interpreter?
While that would be great, it's not necessary we're going to use the
interpreter for debug builds on the device and when
building for release mode we're going to build the boot image and
deploy it to the device.

However it would be great to switch between interpreted mode and
compiled mode during VM construction. So we can use a single library
to link.
But from looking at the code architecture it's seems that this is not
possible right now as most methods related to code execution are not
bound to an interface.

But even more important than this would be a thorough documentation of
the methods in the code. And maybe an improved structure of the
codebase, less use of namespace{} , refactoring C methods into C++
classes. Defines like TARGET_BYTES_PER_WORD should be moved into a
config file that configures the build without having to fall back to
user specified preprocessor macros. Best case is if these things are
based on std. compiler defines i.e:

#ifdef __LP64__
# define TARGET_BYTES_PER_WORD 8
#else
# define TARGET_BYTES_PER_WORD 4
#endif


~Thanks
> > On Feb 29, 2:02�am, Joel Dice <joel.d...@gmail.com> wrote:
> >> On Tue, 28 Feb 2012, Joel Dice wrote:
> >>> On Tue, 28 Feb 2012, Joel Dice wrote:
>
> >>>> �On Tue, 28 Feb 2012, Daniel Jaeger wrote:
>
> >>>>> � Joel,
>
> >>>>> � super! That fixed the problem.
>
> >>>>>> � However, if you're talking about recompiling from
> >>>>>> � source code to byte code on a build server and then loading and
> >>>>>> � running
> >>>>>> � the bytecode on the fly in interpreted mode, yes, that should work.
>
> >>>>> � Yes, that's exactly what I was talking about. I'll try the updated
> >>>>> � hello-project in a minute.
>
> >>>>> � I came across another bug. envTable->DeleteWeakGlobalRef is not
> >>>>> � initialized in jnienv.cpp.
> >>>>> � I also noticed that NewWeakGlobalRef is simply pointing to
> >>>>> � NewGlobalRef.
> >>>>> � Is there any reason you did not implement the WeakRef methods. I'm not
> >>>>> � a Java expert, so I don't know
> >>>>> � if it's OK to not implement these methods. Maybe the official SDK
> >>>>> � actually implements them as stubs.
>
> >>>> �I doubt it. �If the VM claims to support a JNI version greater than or
> >>>> �equal to whichever one introduced {New|Delete}WeakGlobalRef, it should
> >>>> �implement them properly. �However, our process thus far has been to only
> >>>> �implement stuff as necessary to get a given app working. �That way we have
> >>>> �a real-world test case to help make sure we get it right (or at least as
> >>>> �right as that particular use of the feature needs it to be). �I guess
> >>>> �we've just never needed DeleteWeakGlobalRef, and I don't remember why
> >>>> �NewWeakGlobalRef was set to the NewGlobalRef.
>
> >>>> �Anyway, I'll look at what it takes to implement them properly.
>
> >>> I've attached a patch which implements them. �Please let me know if it works
> >>> for you and I'll commit it.
>
> >>> Reading up on the semantics of these methods at
> >>>http://java.sun.com/docs/books/jni/, I feel like the design is inherently
> >>> dangerous. �For example, consider:
>
> >>> �jobject weak = NewWeakGlobalRef(env, foo);
>
> >>> �...
>
> >>> � if (! (*env)->IsSameObject(env, weak, NULL)) {
> >>> � � // DANGER: if a GC occurs after the call to IsSameObject above and
> >>> � � // before the call to CallVoidMethod below, "weak" may be nulled
> >>> � � // out! �Since JNI makes no guarantee about what happens when null
> >>> � � // references are dereferenced
> >>> � � // (http://java.sun.com/docs/books/jni/html/pitfalls.html#11223),
> >>> � � // this could lead to a crash or something similarly unpleasant.
> >>> � � (*env)->CallVoidMethod(env, weak, someMethodID);
> >>> � }
>
> >>> Is there something I'm missing here, or is this really just a fundamentally
> >>> unsafe API?
>
> >> Replying to my reply to myself: yes, the above example is dangerous and
> >> can potentially crash any VM, as explained here:
>
> >> � �http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.htm...
>
> >> The only way to make the above example safe is something like this:
>
> >> � �jobject weak = NewWeakGlobalRef(env, foo);
>
> >> � �...
>
> >> � �jobject strong = NewGlobalRef(env, weak);
> >> � �if (strong) {
> >> � � �(*env)->CallVoidMethod(env, � � strong, someMethodID);
> >> � � �(*env)->DeleteGlobalRef(env, strong);
> >> � �}
>
> >>>>> � However, I can't simply use NewGlobalRef for C++ objects as the GC
> >>>>> � won't be able to collect these retained objects,
> >>>>> � where a Java object is the owner of a C++ instance.
> >>>>> � For these cases I would have used NewWeakGlobalRef.
> >>>>> � Right now I'm not retaining the jobjects in question, but according to
> >>>>> � the JNI-SDK these references can simply change.
>
> >>>>> ~ �Thanks
>
> >>>>> � On Feb 28, 11:39�pm, Joel Dice <joel.d...@gmail.com> wrote:
> >>>>>> � On Tue, 28 Feb 2012, Daniel Jaeger wrote:
> >>>>>>> � Joel,
>
> >>>>>>> � building for iOS with the new makefile without pro-guard works just
> >>>>>>> � fine!
> >>>>>>> � However I can't seem to be able to pass float values to Java.
> >>>>>>> � I've added the following method to the Hello.java file:
>
> >>>>>>> � � �public static void FloatTest(float one, float two, float three)
> >>>>>>> � � �{
> >>>>>>> � � � � � � �System.out.println("one=" + one + " two=" + two + "
> >>>>>>> � three=" +
> >>>>>>> � three);
> >>>>>>> � � �}
>
> >>>>>>> � EOF
>
> >>>>>>> � To invoke the method I added the following code to
> >>>>>>> � "helloAppDelegate.m" line=88
>
> >>>>>>> � � � �jmethodID floatMethod = (*e)->GetMethodID(e, hello,
> >>>>>>> � "FloatTest",
> >>>>>>> � "(FFF)V");
>
> >>>>>>> � � � �if (! (*e)->ExceptionCheck(e)) {
> >>>>>>> � � � � � � � �float one = 123;
> >>>>>>> � � � � � � � �float two = 456;
> >>>>>>> � � � � � � � �float three = 789;
> >>>>>>> � � � � � � � �(*e)->CallStaticVoidMethod(e, hello, floatMethod, one,
> >>>>>>> � two ,
> >>>>>>> � three);
> >>>>>>> � � � �}
>
> >>>>>>> � EOF
>
> >>>>>>> � However this results in the following output:
>
> >>>>>>> � one=0 two=3.48047 three=0
>
> >>>>>>> � The identical code works if doubles are used instead of floats in
> >>>>>>> � both
> >>>>>>> � Java and C.
>
> >>>>>> � Thanks for the report. �It should be fixed as of this commit:
>
> >>>>>> �http://oss.readytalk.com/gitweb?p=avian.git;a=commitdiff;h=5a5d2a8dd2...
>
> >>>>>> � Please let me know if you still have problems.
>
> >>>>>>> � Best,
>
> >>>>>>> � Daniel
>
> >>>>>>> � On Feb 28, 9:06�pm, Daniel Jaeger <dwckongreg...@googlemail.com>
> >>>>>>> � wrote:
> >>>>>>>> � Hi Joel,
>
> >>>>>>>> � thanks for the reply. I'll try the updated hello-iOS example in a
> >>>>>>>> � few
> >>>>>>>> � minutes.
>
> >>>>>>>>> � Yes, if you need to be able to load bytecode at runtime and you
> >>>>>>>>> � don't care
> >>>>>>>>> � much about performance, that will work. �I'll update the
> >>>>>>>>> � hello-ios example
> >>>>>>>>> � makefile to provide this option when I have some time.
>
> >>>>>>>> � That would be amazing. It would be incredibly helpful to be able
> >>>>>>>> � to
> >>>>>>>> � test on the device without having to
> >>>>>>>> � precompile to machine code. Changed code could be pulled from a
> >>>>>>>> � network server and recompiled on the fly.
> >>>>>>>> � Building the boot/code image only for release mode would be the
> >>>>>>>> � best
> >>>>>>>> � possible solution I guess.
>
> >>>>>>>> � Best,
>
> >>>>>>>> � Daniel
>
> >>>>>>>> � On Feb 27, 5:29�pm, Joel Dice
>
> ...
>
> read more »

Joel Dice

unread,
Feb 29, 2012, 9:37:48 PM2/29/12
to Avian
On Wed, 29 Feb 2012, Daniel Jaeger wrote:

> Joel,
>


> ah. I didn't notice that the interpreter was already available, don't
> know how I could miss that.
> I've directly setup my own project files to build the VM with the
> compiler included.
> I just added interpret.cpp and removed architecture specific files and
> it works perfectly.
>
>> If you're asking whether it's possible to build Avian such that a boot
>> image is used for all the classes known at build time, but still allow the
>> interpreter to load classes at runtime, no, that's not currently possible.
> The boot image compiled as machine code, and additional classes in the
> class path executed with the interpreter?
> While that would be great, it's not necessary we're going to use the
> interpreter for debug builds on the device and when
> building for release mode we're going to build the boot image and
> deploy it to the device.

That makes sense.

> However it would be great to switch between interpreted mode and
> compiled mode during VM construction. So we can use a single library
> to link.
> But from looking at the code architecture it's seems that this is not
> possible right now as most methods related to code execution are not
> bound to an interface.

It's actually abstracted pretty nicely. The interface in question is in
processor.h, and the two implementations of that interface are in
interpret.cpp (pure interpreter) and compile.cpp (AOT and JIT
compilation). It would take very little work to allow both
implementations to exist in the same build, as long as you don't expect to
use them both in the same VM instance.

> But even more important than this would be a thorough documentation of
> the methods in the code. And maybe an improved structure of the
> codebase, less use of namespace{} , refactoring C methods into C++
> classes. Defines like TARGET_BYTES_PER_WORD should be moved into a
> config file that configures the build without having to fall back to
> user specified preprocessor macros. Best case is if these things are
> based on std. compiler defines i.e:
>
> #ifdef __LP64__
> # define TARGET_BYTES_PER_WORD 8
> #else
> # define TARGET_BYTES_PER_WORD 4
> #endif

I definitely agree that the documentation could be improved. The need
just hasn't been urgent because there hasn't been a lot of interest in
working on the internals of the VM beyond myself and a few colleagues. I
guess it's a chicken-and-egg problem: the VM needs better documentation to
make it accessible to new developers, but there's little incentive to
write it until those developers dive in and start complaining about the
lack of docs :)

The problem with defining TARGET_BYTES_PER_WORD based on __LP64__, by the
way, is that the target system we're building a boot image for may not be
the same as the one we're building the boot image on. For example, we may
be building a 32-bit Windows boot image on a 64-bit Linux machine, or
vice-versa. You can't rely on built-in macros to figure that out -- it
has to be specified explicitly.

As for reducing the use of anonymous namespaces and moving functions into
classes, I don't see how they would improve the code. There are only two
ways in C++ to indicate to the compiler that a declaration is local to the
compilation unit it appears in: the "static" keyword and anonymous
namespaces. The former has been deprecated for over a decade, so the
latter is the idiomatic way to do it. Also, you can't use the static
keyword on a class definition, but you can put it in an anonymous
namespace.

Whether a non-virtual function resides inside a class or outside seems
like a matter of style to me. It generally comes down to whether you
prefer calls that look like "foo->doSomething()" or "doSomething(foo)".
I tend to prefer the latter for non-virtual functions because you can tell
at a glance that the latter is not a polymorphic call, whereas the former
may or may not be -- you'll need to find out what the type of "foo" is and
then inspect that class to find out. Obviously, there are also good
reasons to put methods inside classes, such as encapsulation and virtual
dispatch, and the VM already makes good use of those features where
appropriate.

>>> On Feb 29, 2:02�am, Joel Dice <joel.d...@gmail.com> wrote:
>>>> On Tue, 28 Feb 2012, Joel Dice wrote:
>>>>> On Tue, 28 Feb 2012, Joel Dice wrote:
>>

>>>>>> �On Tue, 28 Feb 2012, Daniel Jaeger wrote:
>>
>>>>>>> � Joel,
>>

>>>>>>> � super! That fixed the problem.
>>
>>>>>>>> � However, if you're talking about recompiling from
>>>>>>>> � source code to byte code on a build server and then loading and
>>>>>>>> � running
>>>>>>>> � the bytecode on the fly in interpreted mode, yes, that should work.
>>
>>>>>>> � Yes, that's exactly what I was talking about. I'll try the updated
>>>>>>> � hello-project in a minute.
>>
>>>>>>> � I came across another bug. envTable->DeleteWeakGlobalRef is not
>>>>>>> � initialized in jnienv.cpp.
>>>>>>> � I also noticed that NewWeakGlobalRef is simply pointing to
>>>>>>> � NewGlobalRef.
>>>>>>> � Is there any reason you did not implement the WeakRef methods. I'm not
>>>>>>> � a Java expert, so I don't know
>>>>>>> � if it's OK to not implement these methods. Maybe the official SDK
>>>>>>> � actually implements them as stubs.
>>
>>>>>> �I doubt it. �If the VM claims to support a JNI version greater than or
>>>>>> �equal to whichever one introduced {New|Delete}WeakGlobalRef, it should
>>>>>> �implement them properly. �However, our process thus far has been to only
>>>>>> �implement stuff as necessary to get a given app working. �That way we have
>>>>>> �a real-world test case to help make sure we get it right (or at least as
>>>>>> �right as that particular use of the feature needs it to be). �I guess
>>>>>> �we've just never needed DeleteWeakGlobalRef, and I don't remember why
>>>>>> �NewWeakGlobalRef was set to the NewGlobalRef.
>>
>>>>>> �Anyway, I'll look at what it takes to implement them properly.
>>
>>>>> I've attached a patch which implements them. �Please let me know if it works


>>>>> for you and I'll commit it.
>>
>>>>> Reading up on the semantics of these methods at
>>>>> http://java.sun.com/docs/books/jni/, I feel like the design is inherently

>>>>> dangerous. �For example, consider:
>>
>>>>> �jobject weak = NewWeakGlobalRef(env, foo);
>>
>>>>> �...
>>
>>>>> � if (! (*env)->IsSameObject(env, weak, NULL)) {
>>>>> � � // DANGER: if a GC occurs after the call to IsSameObject above and
>>>>> � � // before the call to CallVoidMethod below, "weak" may be nulled
>>>>> � � // out! �Since JNI makes no guarantee about what happens when null
>>>>> � � // references are dereferenced
>>>>> � � // (http://java.sun.com/docs/books/jni/html/pitfalls.html#11223),

>>>>> � � // this could lead to a crash or something similarly unpleasant.
>>>>> � � (*env)->CallVoidMethod(env, weak, someMethodID);
>>>>> � }


>>
>>>>> Is there something I'm missing here, or is this really just a fundamentally
>>>>> unsafe API?
>>
>>>> Replying to my reply to myself: yes, the above example is dangerous and
>>>> can potentially crash any VM, as explained here:
>>

>>>> � �http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.htm...


>>
>>>> The only way to make the above example safe is something like this:
>>

>>>> � �jobject weak = NewWeakGlobalRef(env, foo);
>>
>>>> � �...
>>
>>>> � �jobject strong = NewGlobalRef(env, weak);
>>>> � �if (strong) {
>>>> � � �(*env)->CallVoidMethod(env, � � strong, someMethodID);
>>>> � � �(*env)->DeleteGlobalRef(env, strong);
>>>> � �}
>>
>>>>>>> � However, I can't simply use NewGlobalRef for C++ objects as the GC
>>>>>>> � won't be able to collect these retained objects,
>>>>>>> � where a Java object is the owner of a C++ instance.
>>>>>>> � For these cases I would have used NewWeakGlobalRef.
>>>>>>> � Right now I'm not retaining the jobjects in question, but according to
>>>>>>> � the JNI-SDK these references can simply change.
>>
>>>>>>> ~ �Thanks
>>

>>>>>>> � On Feb 28, 11:39�pm, Joel Dice <joel.d...@gmail.com> wrote:


>>>>>>>> � On Tue, 28 Feb 2012, Daniel Jaeger wrote:
>>>>>>>>> � Joel,
>>

>>>>>>>>>> � On Feb 27, 5:29�pm, Joel Dice
>>
>> ...
>>

Daniel Jaeger

unread,
Mar 1, 2012, 10:31:56 AM3/1/12
to Avian
Joel,

> It's actually abstracted pretty nicely.  The interface in question is in
> processor.h, and the two implementations of that interface are in
> interpret.cpp (pure interpreter) and compile.cpp (AOT and JIT
> compilation).  It would take very little work to allow both
> implementations to exist in the same build, as long as you don't expect to
> use them both in the same VM instance.

That's great to know. I'll look into this then once I get a little
time.

> The problem with defining TARGET_BYTES_PER_WORD based on __LP64__, by the
> way, is that the target system we're building a boot image for may not be
> the same as the one we're building the boot image on.  For example, we may
> be building a 32-bit Windows boot image on a 64-bit Linux machine, or
> vice-versa.  You can't rely on built-in macros to figure that out -- it
> has to be specified explicitly.

Ah, yes, boot-image generator is a special case - but in these cases a
preprocessor define could be used.
In most other cases were building for the architecture we're going to
use I guess.
But that might just make it more confusing as things are defined in
two separate locations.

> As for reducing the use of anonymous namespaces and moving functions into
> classes, I don't see how they would improve the code.  There are only two
> ways in C++ to indicate to the compiler that a declaration is local to the
> ....
> may or may not be -- you'll need to find out what the type of "foo" is and
> then inspect that class to find out.  Obviously, there are also good
> reasons to put methods inside classes, such as encapsulation and virtual
> dispatch, and the VM already makes good use of those features where
> appropriate.

It's definitively "only" a code style question. I tend to prefer
indented code and either a pure c++
codebase or a pure c-codebase. I also noticed your heavy use of the
RUNTIME_ARRAY macro because
the Microsoft compiler does not support dynamically size runtime
arrays. Did you consider using "alloca" for stack allocation instead?
Might make the code easier to read.
~~~
I've finished implementing the VM in our codebase and I'm starting to
work on the bindings but I'm noticing a lot of performance overhead
when calling into native, or from native to VM. Ofcourse I anticipated
some overhead but this is much more than I expected. There is also a
high performance overhead when accessing properties.
Every access is cached during VM startup: jfieldIDs, jclass's and
jmethodIDs.
The following metrics have been done on an iPhone 3GS, with the
project compiled for release mode and using a boot image.

native to vm invocation count=256 time=6ms
native to vm field access count=256 time=3ms

This is pretty heavy as the same code path without the field access
takes only 1.5ms, but with the invocations takes 10ms.
The method invocations could be optimized away in some cases by doing
the following in java code.

if ( javaobject.getfield "overridesthinkmethod" == true )
{
javaobject.invokevm "thinkmethod" (time, deltatime);
}

I'm not sure how Avian handles field access but according to the Sun
docs field access should be quite snappy:
"The overhead of field access using the JNI lies in the cost of
calling through the JNIEnv. Rather than directly dereferencing
objects, the native code has to perform a C function call which in
turn dereferences the object. The function call is necessary because
it isolates the native code from the internal object representation
maintained by the virtual machine implementation. The JNI field access
overhead is typically negligible because a function call takes only a
few cycles."

Could method invocations from java to native be optimized during boot
image generation by directly linking to the native symbol?
So the methodname/-address lookup could be skipped during runtime?
> > On Feb 29, 3:22�am, Joel Dice <joel.d...@gmail.com> wrote:
> >> On Tue, 28 Feb 2012, Daniel Jaeger wrote:
> >>> Joel,
>
> >>> that was fast again. Excellent.
>
> >>> Yes, this is dangerous. But consider the case where the VM creates a
> >>> jobject.
> >>> The object's constructor invokes a native method that creates the
> >>> native class-instance that belongs to the jobject.
> >>> The native class also wants to hold a permanently valid reference to
> >>> the jobject in the VM and vice versa.
> >>> But the reference should not stop the VM from garbage collecting the
> >>> object instance. Thus we need a weak global reference and not a strong
> >>> global reference.
> >>> If the object is being deleted by the VM, the VM will also invoke the
> >>> finalizer and kill the native object (if the user didn't do it
> >>> manually earlier).
>
> >>> ...
>
> >>> I guess even in the example you have posted above it could lead to a
> >>> crash. If the object dies in-between the weak and strong reference
> >>> creations.
>
> >> If that happens, the NewGlobalRef call to create the strong reference will
> >> return null, in which case we won't try to dereference it. �Unless I'm
> >> missing something, there's no possibility of a crash at any point (unless
> >> there's a bug in the VM, of course).
>
> >>> The only solid situation is where the jobject owns the object that
> >>> holds the weak reference : and both die together.
>
> >>> I haven't had time to see if the interpreter run works on the iOS
> >>> devices. Do you pass special preprocessor flags to GCC when building
> >>> an interpreter build or is this something that can be changed during
> >>> runtime? Your related commit didn't seem to change much at all,
> >>> besides specifying the bootclasspath jar.
>
> >> If you specify a process=interpret option to make, the -DBOOT_IMAGE
> >> preprocessor flag will *not* be passed to GCC, which means boot.c will
> >> define a bootJar function instead of the bootimageBin and codeimageBin
> >> functions defined for the bootimage build. �When the VM is initialized, it
> >> will first try to resolve the bootimageBin and codeimageBin functions, but
> >> that will fail in the interpreted build, so it will fall back to a normal
> >> initialization process, using the bootJar function to get access to the
> >> embedded JAR file containing both the application classes and the standard
> >> library classes.
>
> >> If you're asking whether it's possible to build Avian such that a boot
> >> image is used for all the classes known at build time, but still allow the
> >> interpreter to load classes at runtime, no, that's not currently possible.
> >> We can do it on platforms where JIT compilation is available (i.e.
> >> everywhere but iOS), but the non-JIT interpreter is not currently
> >> compatible with boot image builds. �Making it compatible would require
> >> some refactoring, which I'd be willing to pursue if there's enough
> >> interest in it.
>
> >>> Your patch seemed to work great! Thanks.
>
> >>> Daniel
>
> >>> On Feb 29, 2:02�am, Joel Dice <joel.d...@gmail.com> wrote:
> >>>> On Tue, 28 Feb 2012, Joel Dice wrote:
> >>>>> On Tue, 28 Feb 2012, Joel Dice wrote:
>
> >>>>>> �On Tue, 28 Feb 2012, Daniel Jaeger wrote:
>
> >>>>>>> � Joel,
>
> >>>>>>> � super! That fixed the problem.
>
> >>>>>>>> � However, if you're talking about recompiling from
> >>>>>>>> � source code to byte code on a build server and then loading and
> >>>>>>>> � running
> >>>>>>>> � the bytecode on the fly in interpreted mode, yes, that should work.
>
> >>>>>>> � Yes, that's exactly what I was talking about. I'll try the updated
> >>>>>>> � hello-project in a minute.
>
> >>>>>>> � I came across another bug. envTable->DeleteWeakGlobalRef is not
> >>>>>>> � initialized in jnienv.cpp.
> >>>>>>> � I also noticed that NewWeakGlobalRef is simply pointing to
> >>>>>>> � NewGlobalRef.
> >>>>>>> � Is there any reason you did not implement the WeakRef methods. I'm not
> >>>>>>> � a Java expert, so I don't know
> >>>>>>> � if it's OK to not implement these methods. Maybe the official SDK
> >>>>>>> � actually implements them as stubs.
>
> >>>>>> �I doubt it. �If the VM claims to support a JNI version greater than or
> >>>>>> �equal to whichever one introduced {New|Delete}WeakGlobalRef, it should
> >>>>>> �implement them properly. �However, our process thus far has been to only
> >>>>>> �implement stuff as necessary to get a given app working. �That way we have
> >>>>>> �a real-world test case to help make sure we get it right (or at least as
> >>>>>> �right as that particular use of the feature needs it to be). �I guess
> >>>>>> �we've just never needed DeleteWeakGlobalRef, and I don't remember why
> >>>>>> �NewWeakGlobalRef was set to the NewGlobalRef.
>
> >>>>>> �Anyway, I'll look at what it takes to implement them properly.
>
> >>>>> I've attached a patch which implements them. �Please let me know if it works
> >>>>> for you and I'll commit it.
>
> >>>>> Reading up on the semantics of these methods at
> >>>>>http://java.sun.com/docs/books/jni/, I feel like the design is inherently
> >>>>> dangerous. �For example, consider:
>
> >>>>> �jobject weak = NewWeakGlobalRef(env, foo);
>
> >>>>> �...
>
> >>>>> � if (! (*env)->IsSameObject(env, weak, NULL)) {
> >>>>> � � // DANGER: if a GC occurs after the call to IsSameObject above and
> >>>>> � � // before the call to CallVoidMethod below, "weak" may be nulled
> >>>>> � � // out! �Since JNI makes no guarantee about what happens when null
> >>>>> � � // references are dereferenced
> >>>>> � � //
>
> ...
>
> read more »

Joel Dice

unread,
Mar 1, 2012, 1:56:37 PM3/1/12
to Avian
On Thu, 1 Mar 2012, Daniel Jaeger wrote:

> Joel,
>

My understanding is that alloca is function-local, not block-local,
meaning if you call it inside a loop, the stack space will keep growing on
every iteration. That's in contrast with runtime-sized arrays which are
deallocated (or reused) when they go out of scope, not just when the
function containing that scope returns.

> ~~~
> I've finished implementing the VM in our codebase and I'm starting to
> work on the bindings but I'm noticing a lot of performance overhead
> when calling into native, or from native to VM. Ofcourse I anticipated
> some overhead but this is much more than I expected. There is also a
> high performance overhead when accessing properties.
> Every access is cached during VM startup: jfieldIDs, jclass's and
> jmethodIDs.
> The following metrics have been done on an iPhone 3GS, with the
> project compiled for release mode and using a boot image.
>
> native to vm invocation count=256 time=6ms
> native to vm field access count=256 time=3ms
>
> This is pretty heavy as the same code path without the field access
> takes only 1.5ms, but with the invocations takes 10ms.

That surprises me. Are you caching the jmethodIDs and jfieldIDs or
looking them up every time? If you're not caching them, that's probably
where the time is going. If you are, can you send me a standalone test
case so I can profile it?

> The method invocations could be optimized away in some cases by doing
> the following in java code.
>
> if ( javaobject.getfield "overridesthinkmethod" == true )
> {
> javaobject.invokevm "thinkmethod" (time, deltatime);
> }
>
> I'm not sure how Avian handles field access but according to the Sun
> docs field access should be quite snappy:
> "The overhead of field access using the JNI lies in the cost of
> calling through the JNIEnv. Rather than directly dereferencing
> objects, the native code has to perform a C function call which in
> turn dereferences the object. The function call is necessary because
> it isolates the native code from the internal object representation
> maintained by the virtual machine implementation. The JNI field access
> overhead is typically negligible because a function call takes only a
> few cycles."

There's more to it than that, though, when the VM uses a copying garbage
collector. We need to pin the object (or static field table) being
dereferenced in memory so that another thread doesn't try to move it while
we're reading from it. Also, we need to check whether the field is
volatile and, if it is, either synchronize access to it or execute the
appropriate memory barrier(s) to preserve volatile semantics.

Looking at the current implementations of Get*Field and Set*Field, I see
that we are currently using the "run" function to capture the thread state
in case an exception is thrown in the process of getting or setting the
field. If I can prove that no such exception is possible, I ought to be
able to remove that overhead, which should speed up your test case. We
can't do that for method invocations, though, since they may throw
exceptions.

FWIW, you're best bet for maximizing JNI performance with any VM is to
avoid native->java calls and field lookups wherever possible. Just pass
everything to your native method as primitives so it doesn't have to look
up fields in an object or call methods to get more info. If you need to
return more than one value, consider putting them in a primitive array
passed down from the java caller to be filled in by the native method.
Obviously, these techniques don't apply to all cases, but I've found them
to be valuable in general.

> Could method invocations from java to native be optimized during boot
> image generation by directly linking to the native symbol?

Yes, at a cost in code size. We still need to marshal the arguments into
the form dictated by JNI, which is different from the java to java calling
convention used by the VM, so we would need to either generate that
marshalling code at each call site or generate a "thunk" which does the
marshalling and through which all calls to the native method are routed.

If you really want to maximize java to native call speed, you can use an
Avian-specific fast calling convention, but then your native code will
only work with Avian, and you'll need to avoid blocking in such a method,
since the VM won't be able to GC until it returns.

> So the methodname/-address lookup could be skipped during runtime?

The address lookup is cached by the VM, so I doubt that's the bottleneck.

>>> On Feb 29, 3:22�am, Joel Dice <joel.d...@gmail.com> wrote:
>>>> On Tue, 28 Feb 2012, Daniel Jaeger wrote:
>>>>> Joel,
>>
>>>>> that was fast again. Excellent.
>>
>>>>> Yes, this is dangerous. But consider the case where the VM creates a
>>>>> jobject.
>>>>> The object's constructor invokes a native method that creates the
>>>>> native class-instance that belongs to the jobject.
>>>>> The native class also wants to hold a permanently valid reference to
>>>>> the jobject in the VM and vice versa.
>>>>> But the reference should not stop the VM from garbage collecting the
>>>>> object instance. Thus we need a weak global reference and not a strong
>>>>> global reference.
>>>>> If the object is being deleted by the VM, the VM will also invoke the
>>>>> finalizer and kill the native object (if the user didn't do it
>>>>> manually earlier).
>>
>>>>> ...
>>
>>>>> I guess even in the example you have posted above it could lead to a
>>>>> crash. If the object dies in-between the weak and strong reference
>>>>> creations.
>>
>>>> If that happens, the NewGlobalRef call to create the strong reference will

>>>> return null, in which case we won't try to dereference it. �Unless I'm


>>>> missing something, there's no possibility of a crash at any point (unless
>>>> there's a bug in the VM, of course).
>>
>>>>> The only solid situation is where the jobject owns the object that
>>>>> holds the weak reference : and both die together.
>>
>>>>> I haven't had time to see if the interpreter run works on the iOS
>>>>> devices. Do you pass special preprocessor flags to GCC when building
>>>>> an interpreter build or is this something that can be changed during
>>>>> runtime? Your related commit didn't seem to change much at all,
>>>>> besides specifying the bootclasspath jar.
>>
>>>> If you specify a process=interpret option to make, the -DBOOT_IMAGE
>>>> preprocessor flag will *not* be passed to GCC, which means boot.c will
>>>> define a bootJar function instead of the bootimageBin and codeimageBin

>>>> functions defined for the bootimage build. �When the VM is initialized, it


>>>> will first try to resolve the bootimageBin and codeimageBin functions, but
>>>> that will fail in the interpreted build, so it will fall back to a normal
>>>> initialization process, using the bootJar function to get access to the
>>>> embedded JAR file containing both the application classes and the standard
>>>> library classes.
>>
>>>> If you're asking whether it's possible to build Avian such that a boot
>>>> image is used for all the classes known at build time, but still allow the
>>>> interpreter to load classes at runtime, no, that's not currently possible.
>>>> We can do it on platforms where JIT compilation is available (i.e.
>>>> everywhere but iOS), but the non-JIT interpreter is not currently

>>>> compatible with boot image builds. �Making it compatible would require


>>>> some refactoring, which I'd be willing to pursue if there's enough
>>>> interest in it.
>>
>>>>> Your patch seemed to work great! Thanks.
>>
>>>>> Daniel
>>

>>>>> On Feb 29, 2:02�am, Joel Dice <joel.d...@gmail.com> wrote:
>>>>>> On Tue, 28 Feb 2012, Joel Dice wrote:
>>>>>>> On Tue, 28 Feb 2012, Joel Dice wrote:
>>

>>>>>>>> �On Tue, 28 Feb 2012, Daniel Jaeger wrote:
>>
>>>>>>>>> � Joel,
>>

>>>>>>>>> � super! That fixed the problem.
>>
>>>>>>>>>> � However, if you're talking about recompiling from
>>>>>>>>>> � source code to byte code on a build server and then loading and
>>>>>>>>>> � running
>>>>>>>>>> � the bytecode on the fly in interpreted mode, yes, that should work.
>>
>>>>>>>>> � Yes, that's exactly what I was talking about. I'll try the updated
>>>>>>>>> � hello-project in a minute.
>>
>>>>>>>>> � I came across another bug. envTable->DeleteWeakGlobalRef is not
>>>>>>>>> � initialized in jnienv.cpp.
>>>>>>>>> � I also noticed that NewWeakGlobalRef is simply pointing to
>>>>>>>>> � NewGlobalRef.
>>>>>>>>> � Is there any reason you did not implement the WeakRef methods. I'm not
>>>>>>>>> � a Java expert, so I don't know
>>>>>>>>> � if it's OK to not implement these methods. Maybe the official SDK
>>>>>>>>> � actually implements them as stubs.
>>
>>>>>>>> �I doubt it. �If the VM claims to support a JNI version greater than or
>>>>>>>> �equal to whichever one introduced {New|Delete}WeakGlobalRef, it should
>>>>>>>> �implement them properly. �However, our process thus far has been to only
>>>>>>>> �implement stuff as necessary to get a given app working. �That way we have
>>>>>>>> �a real-world test case to help make sure we get it right (or at least as
>>>>>>>> �right as that particular use of the feature needs it to be). �I guess
>>>>>>>> �we've just never needed DeleteWeakGlobalRef, and I don't remember why
>>>>>>>> �NewWeakGlobalRef was set to the NewGlobalRef.
>>
>>>>>>>> �Anyway, I'll look at what it takes to implement them properly.
>>
>>>>>>> I've attached a patch which implements them. �Please let me know if it works


>>>>>>> for you and I'll commit it.
>>
>>>>>>> Reading up on the semantics of these methods at
>>>>>>> http://java.sun.com/docs/books/jni/, I feel like the design is inherently

>>>>>>> dangerous. �For example, consider:
>>
>>>>>>> �jobject weak = NewWeakGlobalRef(env, foo);
>>
>>>>>>> �...
>>
>>>>>>> � if (! (*env)->IsSameObject(env, weak, NULL)) {
>>>>>>> � � // DANGER: if a GC occurs after the call to IsSameObject above and
>>>>>>> � � // before the call to CallVoidMethod below, "weak" may be nulled
>>>>>>> � � // out! �Since JNI makes no guarantee about what happens when null
>>>>>>> � � // references are dereferenced
>>>>>>> � � //
>>

Daniel Jaeger

unread,
Mar 1, 2012, 3:25:56 PM3/1/12
to Avian
> My understanding is that alloca is function-local, not block-local,
> meaning if you call it inside a loop, the stack space will keep growing on
> every iteration. That's in contrast with runtime-sized arrays which are
> deallocated (or reused) when they go out of scope, not just when the
> function containing that scope returns.
Yes, that's true, alloca blocks stay around until the function returns
to the caller.

> That surprises me. Are you caching the jmethodIDs and jfieldIDs or
> looking them up every time? If you're not caching them, that's probably
> where the time is going. If you are, can you send me a standalone test
> case so I can profile it?
Yes, as said. Both are cached when the VM starts. This is exactly the
code from the
engine:

vmObject_t *vmObject = object->GetValue();
vmField_t *vmField = field->GetValue();

PROFILE_FIELD_GETTER_START();
bool value = _environment->GetBooleanField(vmObject, vmField);
PROFILE_FIELD_GETTER_STOP();

return value;

The performance penalty scales up nearly linearly, with 1000 field
lookups it's around 10ms.
It's actually only a bool, so I wouldn't have thought that there are
deeper lookups involved.
What kind of test case are you looking for? A binary? But as you can
see in the code above, nothing
really dramatic happens.

> There's more to it than that, though, when the VM uses a copying garbage
> collector. We need to pin the object (or static field table) being
> dereferenced in memory so that another thread doesn't try to move it while
> we're reading from it. Also, we need to check whether the field is
> volatile and, if it is, either synchronize access to it or execute the
> appropriate memory barrier(s) to preserve volatile semantics.
Are there different GC implementations available right now? Execution
performance is most important to us,
games typically only allocate new objects during initialization or
restart. So if there is a garbage collector available
that trades allocation speed for faster lookups that'd be great.

> Looking at the current implementations of Get*Field and Set*Field, I see
> that we are currently using the "run" function to capture the thread state
> in case an exception is thrown in the process of getting or setting the
> field. If I can prove that no such exception is possible, I ought to be
> able to remove that overhead, which should speed up your test case. We
> can't do that for method invocations, though, since they may throw
> exceptions.
Maybe it would be a good idea, at least for us, to introduce unsafe
methods for
property and method access then.

<snip>

I just tested the performance if we're not going down the run route,
and we're not locking the field etc.:

jboolean JNICALL GetBooleanField(Thread* t, jobject o, jfieldID
field)
{
return cast<jboolean>(*o, fieldOffset(t, getField(t, field)));
}

This greatly improves performance, 1000 lookups down to 3msec. Our
scenario is that we're calling
into the VM synchronously whenever the engine's Think... or Render...
methods occur. I guess there are a lot
of possibilities to improve performance at the cost of risking
stability. But I think this situation allows
us to be unsafe regarding object locks here, especially when getting/
settings primitives.
But I'm not sure about it, as I don't know how the actual VM works.
Maybe you can clear this up:
We will only call the getter/setter methods from the native side of
the object in a synchronous fashion and
if the vm object is the owner of the native object.
Basically we can be sure that it won't be gc'd else we wouldn't be
running through its think method.
If we're running through a think method the object has to be added to
a collection that is global i.e. at the end of the hierarchy it must
be added to the main
static container object enabling the game loop.

Besides doing all the calls into the VM synchronously i.e. either
native game code or java code executed,
we're also running the engine and all calls into the vm on a single
thread.
Other parts that might run on a separate thread will never have the
ability to call into the vm at all (i.e. the graphics renderer)

If we're not using a copying garbage collector, could we store a
pointer to the reference obtained through the following call?

fieldOffset(t, getField(t, field)))

As said, we're operating on objects owned by the VM object so we're
sure it will be alive when we're getting properties.

> FWIW, you're best bet for maximizing JNI performance with any VM is to
> avoid native->java calls and field lookups wherever possible. Just pass
> everything to your native method as primitives so it doesn't have to look
> up fields in an object or call methods to get more info. If you need to
> return more than one value, consider putting them in a primitive array
> passed down from the java caller to be filled in by the native method.
> Obviously, these techniques don't apply to all cases, but I've found them
> to be valuable in general.
Yes, that is exactly what I'm trying to do, avoid lookups wherever
possible.
About passing arrays. Consider that we have to fill multiple float[3]
fields of an object at once.
Would it be faster to create a large primitive array and fill it with
all fields and pass it to the vm
or simply set all float[3] arrays using the SetField methods? I guess
if we can do it without
locking and doing a "run" then primitive-locking-free setters would be
faster.


> Yes, at a cost in code size. We still need to marshal the arguments into
> the form dictated by JNI, which is different from the java to java calling
> convention used by the VM, so we would need to either generate that
> marshalling code at each call site or generate a "thunk" which does the
> marshalling and through which all calls to the native method are routed.

Would it make sense to implement it performance wise?

> If you really want to maximize java to native call speed, you can use an
> Avian-specific fast calling convention, but then your native code will
> only work with Avian, and you'll need to avoid blocking in such a method,
> since the VM won't be able to GC until it returns.

That's no problem, if everything continues to work out as it does
right now,
we're going to stick with Avian. We're really fond of the lightweight
codebase.
What has to be changed to export methods to be Avian compatible?

Thanks again for your time and dedication, this is outstanding.




Joel Dice

unread,
Mar 1, 2012, 8:01:44 PM3/1/12
to Avian
On Thu, 1 Mar 2012, Daniel Jaeger wrote:

>> My understanding is that alloca is function-local, not block-local,
>> meaning if you call it inside a loop, the stack space will keep growing on
>> every iteration. That's in contrast with runtime-sized arrays which are
>> deallocated (or reused) when they go out of scope, not just when the
>> function containing that scope returns.
> Yes, that's true, alloca blocks stay around until the function returns
> to the caller.
>
>> That surprises me. Are you caching the jmethodIDs and jfieldIDs or
>> looking them up every time? If you're not caching them, that's probably
>> where the time is going. If you are, can you send me a standalone test
>> case so I can profile it?
> Yes, as said. Both are cached when the VM starts. This is exactly the
> code from the
> engine:
>

Sorry, I should have read more carefully.

> vmObject_t *vmObject = object->GetValue();
> vmField_t *vmField = field->GetValue();
>
> PROFILE_FIELD_GETTER_START();
> bool value = _environment->GetBooleanField(vmObject, vmField);
> PROFILE_FIELD_GETTER_STOP();
>
> return value;
>
> The performance penalty scales up nearly linearly, with 1000 field
> lookups it's around 10ms.
> It's actually only a bool, so I wouldn't have thought that there are
> deeper lookups involved.
> What kind of test case are you looking for? A binary? But as you can
> see in the code above, nothing
> really dramatic happens.

I guess what I'm looking for is a realistic benchmark that I can profile.
Obviously, calling GetBooleanField in a tight loop will make it prominent
in a profile, but that can be addressed by just making one call before
entering the loop. I'd like to see a realistic design where Get*Field is
a bottleneck such that it can't be easily redesigned in such a way that
said method is no longer a bottleneck. I'm not claiming such a case is
unrealistic, but I'm having a hard time imagining it.

>> There's more to it than that, though, when the VM uses a copying garbage
>> collector. We need to pin the object (or static field table) being
>> dereferenced in memory so that another thread doesn't try to move it while
>> we're reading from it. Also, we need to check whether the field is
>> volatile and, if it is, either synchronize access to it or execute the
>> appropriate memory barrier(s) to preserve volatile semantics.
> Are there different GC implementations available right now? Execution
> performance is most important to us,
> games typically only allocate new objects during initialization or
> restart. So if there is a garbage collector available
> that trades allocation speed for faster lookups that'd be great.

No, sorry -- there's only one GC. Anyway, we ought to be able to make the
fast path fast enough and still safe with the current GC, especially for
workloads where collections are rare.

>> Looking at the current implementations of Get*Field and Set*Field, I see
>> that we are currently using the "run" function to capture the thread state
>> in case an exception is thrown in the process of getting or setting the
>> field. If I can prove that no such exception is possible, I ought to be
>> able to remove that overhead, which should speed up your test case. We
>> can't do that for method invocations, though, since they may throw
>> exceptions.
> Maybe it would be a good idea, at least for us, to introduce unsafe
> methods for
> property and method access then.
>
> <snip>
>
> I just tested the performance if we're not going down the run route,
> and we're not locking the field etc.:
>
> jboolean JNICALL GetBooleanField(Thread* t, jobject o, jfieldID
> field)
> {
> return cast<jboolean>(*o, fieldOffset(t, getField(t, field)));
> }

That might crash if another thread, during GC, deallocates the memory
previously allocated for that object while you're trying to dereference
it. Adding "ENTER(t, Thread::ActiveState);" right above the dereference
would fix that and shouldn't hurt performance much. It still won't be
correct for volatile fields, but it won't crash.

FWIW, this is what Avian's implementation used to look like:

jboolean JNICALL
GetBooleanField(Thread* t, jobject o, jfieldID field)
{

ENTER(t, Thread::ActiveState);

return cast<jboolean>(*o, field);
}

Back then, "field" was just an offset. It was only later that we
supported volatile fields properly, for which we need to look up the
VMField instance to check whether it is volatile or not. I guess we could
go back to the old way and just treat all fields as volatile. Might be an
interesting experiment.

> This greatly improves performance, 1000 lookups down to 3msec. Our
> scenario is that we're calling
> into the VM synchronously whenever the engine's Think... or Render...
> methods occur. I guess there are a lot
> of possibilities to improve performance at the cost of risking
> stability. But I think this situation allows
> us to be unsafe regarding object locks here, especially when getting/
> settings primitives.
> But I'm not sure about it, as I don't know how the actual VM works.
> Maybe you can clear this up:
> We will only call the getter/setter methods from the native side of
> the object in a synchronous fashion and
> if the vm object is the owner of the native object.
> Basically we can be sure that it won't be gc'd else we wouldn't be
> running through its think method.

It doesn't matter if it's reachable or not; objects may be moved during GC
and the old memory deleted, so any thread which tries to dereference the
old pointer will potentially crash. This is true even if the object
remains alive, since it's now in a new location.

> If we're running through a think method the object has to be added to
> a collection that is global i.e. at the end of the hierarchy it must
> be added to the main
> static container object enabling the game loop.
>
> Besides doing all the calls into the VM synchronously i.e. either
> native game code or java code executed,
> we're also running the engine and all calls into the vm on a single
> thread.
> Other parts that might run on a separate thread will never have the
> ability to call into the vm at all (i.e. the graphics renderer)

That will avoid the type of crash I described above, but it does limit
your options for multithreading in the future, and I can't guarantee Avian
might not someday launch internal threads which may trigger GCs.

>
> If we're not using a copying garbage collector, could we store a
> pointer to the reference obtained through the following call?
>
> fieldOffset(t, getField(t, field)))

Yes, that works if the GC never moves objects.

> As said, we're operating on objects owned by the VM object so we're
> sure it will be alive when we're getting properties.
>
>> FWIW, you're best bet for maximizing JNI performance with any VM is to
>> avoid native->java calls and field lookups wherever possible. Just pass
>> everything to your native method as primitives so it doesn't have to look
>> up fields in an object or call methods to get more info. If you need to
>> return more than one value, consider putting them in a primitive array
>> passed down from the java caller to be filled in by the native method.
>> Obviously, these techniques don't apply to all cases, but I've found them
>> to be valuable in general.
> Yes, that is exactly what I'm trying to do, avoid lookups wherever
> possible.
> About passing arrays. Consider that we have to fill multiple float[3]
> fields of an object at once.
> Would it be faster to create a large primitive array and fill it with
> all fields and pass it to the vm
> or simply set all float[3] arrays using the SetField methods? I guess
> if we can do it without
> locking and doing a "run" then primitive-locking-free setters would be
> faster.

Filling a large array and passing it to the VM will be faster currently,
and even if we optimize Set*Field as much as we can, I think the large
array strategy will still be faster.

>
>> Yes, at a cost in code size. We still need to marshal the arguments into
>> the form dictated by JNI, which is different from the java to java calling
>> convention used by the VM, so we would need to either generate that
>> marshalling code at each call site or generate a "thunk" which does the
>> marshalling and through which all calls to the native method are routed.
>
> Would it make sense to implement it performance wise?

Hard to say without trying it and profiling a realistic test case. I've
never encountered an app for which native call overhead was a bottleneck;
usually each call has enough work to do that the overhead was
insignificant compared to the time it took to do the work itself. I don't
do much game programming, though :)

>> If you really want to maximize java to native call speed, you can use an
>> Avian-specific fast calling convention, but then your native code will
>> only work with Avian, and you'll need to avoid blocking in such a method,
>> since the VM won't be able to GC until it returns.
>
> That's no problem, if everything continues to work out as it does
> right now,
> we're going to stick with Avian. We're really fond of the lightweight
> codebase.
> What has to be changed to export methods to be Avian compatible?

You can see some examples in builtin.cpp. The naming convention is just
like for JNI, except the prefix is "Avian_", not "Java_". Functions using
this convention always take three arguments and return int64_t or void.
For example:

extern "C" JNIEXPORT int64_t JNICALL
Avian_avian_resource_Handler_00024ResourceInputStream_read__JI_3BII
(Thread* t, object /*method*/, uintptr_t* arguments)
{
// t is the thread-local state

// method is a pointer to the VMMethod object being called

// arguments is an array of arguments being passed to the method, where
// longs (i.e. 64-bit values) always take two slots, regardless of
// whether we're on a 32-bit or 64-bit platform.

int64_t peer; memcpy(&peer, arguments, 8);
int32_t position = arguments[2];
object buffer = reinterpret_cast<object>(arguments[3]);
int32_t offset = arguments[4];
int32_t length = arguments[5];

if (length == 0) return 0;

System::Region* region = reinterpret_cast<System::Region*>(peer);
if (length > static_cast<jint>(region->length()) - position) {
length = static_cast<jint>(region->length()) - position;
}
if (length <= 0) {
return -1;
} else {
memcpy(&byteArrayBody(t, buffer, offset), region->start() + position,
length);
return length;
}
}

> Thanks again for your time and dedication, this is outstanding.

No problem; I'm excited to see Avian being used this way.

Daniel Jaeger

unread,
Mar 2, 2012, 7:32:05 AM3/2/12
to Avian
> I guess what I'm looking for is a realistic benchmark that I can profile.
> Obviously, calling GetBooleanField in a tight loop will make it prominent in a profile,
> but that can be addressed by just making one call before entering the loop.
> I'd like to see a realistic design where Get*Field is a bottleneck such that it can't be easily
> redesigned in such a way that said method is no longer a bottleneck.
> I'm not claiming such a case is unrealistic, but I'm having a hard time imagining it.
Game logic/render updates are a really tight loop. A small example;
before rendering an object
we have to vist it's prerender method and check wheter it's
transformation matrix needs to be updated
because a value that's related to the transformation changed. Now with
the scripting in place we have
to check if the transformation update was initiated through java, if
yes, the we have to pull
the changed values from java and push it into our object, if not, we
have to push the changes
into java. These things are already deferred, so they happen at a
maximum only once per frame per visible object.
The number I posted were not the result of a simple for(…) they are
the result of rendering a scene
with 1000 objects visible.

> That might crash if another thread, during GC, deallocates the memory previously allocated for that object while you're trying to dereference it.
> Adding "ENTER(t, Thread::ActiveState);" right above the dereference would fix that and shouldn't hurt performance much.
> It still won't be correct for volatile fields, but it won't crash.
Locking the threadstate to avoid GC'ing on every field access is not
the proper solution for us I guess.
I think locking the thread state when leaving the VM is probably
better, so we can perform "Get**FieldUnsafe" method invocations and we
only have to do it once. Locking the thread state with ENTER(t,
Thread::ActiveState); on every field access seems to introduce to
much performance overhead in situations where we're sure we're the
only one accessing the vm. Maybe it's even better to fully halt the
entire VM and all it's threads. How can all threads be stopped right
now? Is there a facility to do this? Or should we just lock the
Thread::ActiveState when we're done with the VM code and release the
ENTER(t, Thread::ActiveState); resource when we're about to enter java
code again?


> Back then, "field" was just an offset. It was only later that we supported volatile fields properly, for which we need to look up the VMField instance to
> check whether it is volatile or not. I guess we could go back to the old way and just treat all fields as volatile. Might be an interesting experiment.
Adding this would be great, or add a fastpath to the field offset
without having to lookup the field type for the "S/Get**FieldUnsafe"
I guess simply returning the field offset to the caller of GetFieldID
should work, of course we also have to modify all Get/Set calls to how
you have done it.
And if we need to convert this "fieldOffset" into the actual vm field
we could just traverse the field table again and see which field
matches the offset.
Alternatively we could introduce a vmFastField_t that is basically
just the offset, calling conventions to GetBooleanFieldFast() would
require a vmFastField and not a vmField. vmFastField_t should also
ignore volatile fields, or maybe throw an exception if a
GetFastFieldID is invoked on a volatile field.


>> … could we store a pointer to the reference obtained through the following call?
>> fieldOffset(t, getField(t, field)))
> Yes, that works if the GC never moves objects.
Do fieldOffsets change? then what I posted above would not work. After
looking at the code I though they're just a constant defining a
relative value depending on the layout of the class? How could: "
return cast<jboolean>(*o, field); " have worked then if the GC moves
field offsets around?


> Filling a large array and passing it to the VM will be faster currently, and even if we optimize Set*Field as much as we can,
> I think the large array strategy will still be faster.
Even if we just add then dereference pointers? Isn't that nearly as
fast as it can get in such cases? After all to alloc and pass the
array we have
to invoke quite a lot JNI calls.
Keep in mind that we might have busy scenes where thousands of objects
want their state transform updated.

NewFloatArray(…) // create array
SetFloatArrayRegion(…) // fill in change set
NewObjectArray(…) // create object array containing objects that
changed,
for each object
SetObjectArrayElement() // fill in object that contain changes
InvokeMethodVM(commit changes)

And once the java vm is in the commit changes method, it still has to
pull out each object and commit the change set to the java object.
Basically it does the same thing as we could have done directly from
native code, assigning the values to the fields.


> Hard to say without trying it and profiling a realistic test case. I've never encountered an app for which native call overhead was a bottleneck; usually
> each call has enough work to do that the overhead was insignificant compared to the time it took to do the work itself.
> I don't do much game programming, though :)
Well executing small "think" methods that just define how the object
and/or it's child behave, or state transforms, vector operations,
basically a lot of these things.


> You can see some examples in builtin.cpp. The naming convention is just like for JNI, except the prefix is "Avian_", not "Java_". Functions using this
> convention always take three arguments and return int64_t or void. For example:
Thanks we'll use the Avian native conventions then.




Daniel Jaeger

unread,
Mar 2, 2012, 5:15:09 PM3/2/12
to Avian
Joel,

we're seeing crashes when using the boot image on the workstation and
on the device when calling from java to native and back to java. It
doesn't happen every time and it's hard to trace.
Everything works fine in compiled non-bootimage builds and with the
interpreter.
I also noticed that restarting the VM (killing and creating a new
instance) is impossible when using a boot image.

~Thanks

Joel Dice

unread,
Mar 2, 2012, 7:15:28 PM3/2/12
to Avian
On Fri, 2 Mar 2012, Daniel Jaeger wrote:

>> I guess what I'm looking for is a realistic benchmark that I can profile.
>> Obviously, calling GetBooleanField in a tight loop will make it prominent in a profile,
>> but that can be addressed by just making one call before entering the loop.
>> I'd like to see a realistic design where Get*Field is a bottleneck such that it can't be easily
>> redesigned in such a way that said method is no longer a bottleneck.
>> I'm not claiming such a case is unrealistic, but I'm having a hard time imagining it.
> Game logic/render updates are a really tight loop. A small example;
> before rendering an object
> we have to vist it's prerender method and check wheter it's
> transformation matrix needs to be updated
> because a value that's related to the transformation changed. Now with
> the scripting in place we have
> to check if the transformation update was initiated through java, if
> yes, the we have to pull
> the changed values from java and push it into our object, if not, we
> have to push the changes
> into java. These things are already deferred, so they happen at a
> maximum only once per frame per visible object.
> The number I posted were not the result of a simple for(…) they are
> the result of rendering a scene
> with 1000 objects visible.

Thanks, that helps me understand the problem better. Now, if you're
willing and able to provide a concrete example of this workload which I
can build and profile, I'll be happy to help optimize it. What I'm trying
to avoid is micro-optimization without measurement and feedback from a
realistic test case. If I try to write such a test case based on what
you've told me, the result might not emulate your workload, whereas you
know how to get it right the first time. No pressure, but it would make
it much easier for me to provide useful advice and improvements.

>> That might crash if another thread, during GC, deallocates the memory previously allocated for that object while you're trying to dereference it.
>> Adding "ENTER(t, Thread::ActiveState);" right above the dereference would fix that and shouldn't hurt performance much.
>> It still won't be correct for volatile fields, but it won't crash.
> Locking the threadstate to avoid GC'ing on every field access is not
> the proper solution for us I guess.
> I think locking the thread state when leaving the VM is probably
> better, so we can perform "Get**FieldUnsafe" method invocations and we
> only have to do it once. Locking the thread state with ENTER(t,
> Thread::ActiveState); on every field access seems to introduce to
> much performance overhead in situations where we're sure we're the
> only one accessing the vm. Maybe it's even better to fully halt the
> entire VM and all it's threads. How can all threads be stopped right
> now? Is there a facility to do this? Or should we just lock the
> Thread::ActiveState when we're done with the VM code and release the
> ENTER(t, Thread::ActiveState); resource when we're about to enter java
> code again?

Yes, "ENTER(t, Thread::ExclusiveState)" will ensure you're the only
VM-attached thread running, but that's expensive and unecessary for your
case. As long as you're in Thread::ActiveState, you can be sure no other
thread will GC.

Anyway, if you use the Avian calling convention, you'll already be in
ActiveState, so you get that for free. That's one of the nice things
about the convention - no state transitions at all when crossing the
native/java boundary, whereas the JNI convention requires the
conservative assumption that the native call will block, which means we
have to transtion to IdleState for the call and then back to ActiveState
before returning to Java.

>>> … could we store a pointer to the reference obtained through the following call?
>>> fieldOffset(t, getField(t, field)))
>> Yes, that works if the GC never moves objects.
> Do fieldOffsets change? then what I posted above would not work. After
> looking at the code I though they're just a constant defining a
> relative value depending on the layout of the class? How could: "
> return cast<jboolean>(*o, field); " have worked then if the GC moves
> field offsets around?

No, the field offsets never change for a given class. In fact, we
calculate them ahead of time for classes the VM uses internally, so
they're compile-time constants. Thus, a call to e.g. vm::stringData(t,
myString) is no more expensive after inlining and optimization than a C
struct field dereference through a pointer, e.g. myString->data. If you
need every last bit of performance, you may be able to use the type
generator (type-generator.cpp) to generate such inlineable functions for
your application, too.

>
>> Filling a large array and passing it to the VM will be faster currently, and even if we optimize Set*Field as much as we can,
>> I think the large array strategy will still be faster.
> Even if we just add then dereference pointers? Isn't that nearly as
> fast as it can get in such cases? After all to alloc and pass the
> array we have
> to invoke quite a lot JNI calls.
> Keep in mind that we might have busy scenes where thousands of objects
> want their state transform updated.
>
> NewFloatArray(…) // create array
> SetFloatArrayRegion(…) // fill in change set
> NewObjectArray(…) // create object array containing objects that
> changed,
> for each object
> SetObjectArrayElement() // fill in object that contain changes
> InvokeMethodVM(commit changes)
>
> And once the java vm is in the commit changes method, it still has to
> pull out each object and commit the change set to the java object.
> Basically it does the same thing as we could have done directly from
> native code, assigning the values to the fields.

I see now. Yes, you're right; doing it directly in native code should be
faster.

Joel Dice

unread,
Mar 2, 2012, 8:04:07 PM3/2/12
to Avian
On Fri, 2 Mar 2012, Daniel Jaeger wrote:

> Joel,
>
> we're seeing crashes when using the boot image on the workstation and
> on the device when calling from java to native and back to java. It
> doesn't happen every time and it's hard to trace.

Can you post a backtrace from GDB or LLDB?

> Everything works fine in compiled non-bootimage builds and with the
> interpreter.
> I also noticed that restarting the VM (killing and creating a new
> instance) is impossible when using a boot image.

It's not impossible. We're doing it in an app I'm helping to develop.
However, you need to make sure you call DestroyJavaVM before creating the
new instance. If any other non-daemon threads are still running when
DestroyJavaVM is called, that call will block until they exit.

Daemon threads are trickier. The VM won't wait from them to exit before
returning from a call to DestroyJavaVM, but we can't completely clean up a
VM instance until every attached thread, daemon or otherwise, has exited
or detached. Furthermore, we cannot safely force daemon threads to exit
in general, since they could be blocked in native code which will
eventually try to return control to the VM. The upshot is that the VM may
not actually be destroyed as soon as DestroyJavaVM returns if there are
still daemon threads running. I don't know how other VMs handle this

Ideally, the above wouldn't matter much, because we ought to be able to
support multiple VM instances concurrently active in the same VM and even
allow a given thread to attach to multiple instances simultaneously.
Indeed, Avian is highly reentrant in all but two or three places, which
are:

1. Signal handling (for e.g. NullPointerExceptions and
ArithmeticExceptions): multiple instances would need to cooperate so
that we could figure out which one a given signal should be delivered
to. The current code uses a global pointer to the current instance,
but we could turn that into an array and make the signal handler
offer the signal to each VM instance in the array until one of them
accepts it.

2. Sharing a heap image: currently, the heap image is mutable so we can
change relative pointers to absolute ones during VM initialization
and then later mark references for visiting during GC. If multiple
instances tried to do those things simultaneously, chaos would ensue.
We could fix this by storing a compressed version of the heap image
in the binary and letting each VM instance decompress it into its own
private area of memory.

3. (OpenJDK port only) Some of the JVM_* functions used by the OpenJDK
class library to call into the VM do not take JNIEnv* or JavaVM*
parameters, so we have to use global or thread-local variables to
figure out which instance they are intended for. Very annoying, and
I'm not sure how we could work around this.

Anyway, the bottom line is you currently can't have more than one
VM instance alive at a time, and you must make sure all your threads have
exited or detached before you call DestroyJavaVM to ensure that the
instance is really dead.

Joel Dice

unread,
Mar 2, 2012, 8:11:17 PM3/2/12
to Avian
On Fri, 2 Mar 2012, Joel Dice wrote:

> Ideally, the above wouldn't matter much, because we ought to be able to
> support multiple VM instances concurrently active in the same VM and even

Sorry, that should read "active in the same process".

Daniel Jaeger

unread,
Mar 3, 2012, 8:54:18 AM3/3/12
to Avian
> Thanks, that helps me understand the problem better. Now, if you're
> willing and able to provide a concrete example of this workload which I
> can build and profile, I'll be happy to help optimize it. What I'm trying
> to avoid is micro-optimization without measurement and feedback from a
> realistic test case. If I try to write such a test case based on what
> you've told me, the result might not emulate your workload, whereas you
> know how to get it right the first time. No pressure, but it would make
> it much easier for me to provide useful advice and improvements.
Unfortunately I can't provide you with our source code, but I could
probably
send you a binary build of the engine. Maybe you can inject/hook the c
api creation
of our built-in vm and simply inject a call to create your vm with
code you can debug/profile.
This engine branch currently builds on OSX, but I can probably setup a
win32 build for you.


> Yes, "ENTER(t, Thread::ExclusiveState)" will ensure you're the only
> VM-attached thread running, but that's expensive and unnecessary for your
> case.
That's good, I can't stress how important it is that we've got full
control
of the VM and it's threading. Is there another way to really halt all
vm threads
i.e. if the user creates a Thread instance in a java class, that
thread should also be halted.
See below, I already noticed the VM spawns threads on it's own.

> No, the field offsets never change for a given class. In fact, we
> calculate them ahead of time for classes the VM uses internally, so
> they're compile-time constants. Thus, a call to e.g. vm::stringData(t,
> myString) is no more expensive after inlining and optimization than a C
> struct field dereference through a pointer, e.g. myString->data. If you
> need every last bit of performance, you may be able to use the type
> generator (type-generator.cpp) to generate such inlineable functions for
> your application, too.
That's good to know, that opens up a lot of room for performance
improvements!
Another thing that would be great if it's possible to do the same for
java code in the vm, i.e.
direct access to compile-time pre-mapped fields in native C structs.

>> we're seeing crashes when using the boot image on the workstation and
>> on the device when calling from java to native and back to java. It
>> doesn't happen every time and it's hard to trace.
> Can you post a backtrace from GDB or LLDB?
Unfortunately not a useful one, the engine dies hard and gdb returns
"can't unwind past frame"
However I've already traced it manually, the last thing the engine
does is call the native alloc method in the constructor of a java
object.
The VM seems to not be able to return back into the VM. Again, this
only happens with the boot image!
This is the engine output, the "resolved=" output is when the vm
resolves native address's.
I've added some annotations:

>>>
resolved=bootimageBin at=0x16d480
resolved=codeimageBin at=0x16d4c0
...FizzPop active (x86)
// call init method of appbase class
resolved=Java_drahtkern_System_Console_WriteLine at=0x170e10
App initialized
// create instance of Main node
resolved=Java_drahtkern_Game_Node_NativeAlloc at=0x172ef0
main node allocated
// create instance of CameraNode
resolved=Java_java_lang_System_currentTimeMillis at=0x168fd0
resolved=Avian_java_lang_System_arraycopy at=0x17ced0
resolved=Java_java_lang_System_getProperty at=0x169370
resolved=Java_java_io_FileOutputStream_write__I_3BII at=0x167430
call to super (NodeBase())
resolved=Java_drahtkern_Game_CameraNode_NativeAlloc at=0x176ef0
native alloc done
* crash
Previous frame inner to this frame (gdb could not unwind past this
frame)(gdb)
(gdb) bt
#0 0x002e9cf4 in _binary_codeimage_bin_start ()
#1 0x00000021 in ?? ()
<<<

All other threads contain the same bt:
>>>
(gdb) bt
#0 0x93c4b02e in __workq_kernreturn ()
#1 0x9865accf in _pthread_wqthread ()
#2 0x9865c6fe in start_wqthread ()
<<<

This is the corresponding java code:
>>>
public static void Initialize()
{
Console.WriteLine("App initialized");

_main = new Node();

Console.WriteLine("main node allocated");

// create a 3d perspective camera node
// the CameraNode origin + rotation define the origin and rotation
of
// the camera
_scene = new CameraNode();
… crash
<<<

This is what happens in the CameraNode constructor:
>>>
public CameraNode()
{
super();

System.out.println("call to super (NodeBase())");

_peer = NativeAlloc();
System.out.println("native alloc done");

_transformation[3] = -90;

System.out.println("transform");
}
<<<
The VM dies before assigning the array value. For the sake of
completeness
here is the constructor of the NodeBase super class:
>>>
public NodeBase()
{
_parent = null;
_isTransformUpdateRequired = false;

// origin
_transformation = new float[9];
_transformation[0] = 0;
_transformation[1] = 0;
_transformation[2] = 0;

// rotation
_transformation[3] = 0;
_transformation[4] = 0;
_transformation[5] = 0;

// scale
_transformation[6] = 1.0f;
_transformation[7] = 1.0f;
_transformation[8] = 1.0f;
}
<<<



>> I also noticed that restarting the VM (killing and creating a new
>> instance) is impossible when using a boot image.
> It's not impossible. We're doing it in an app I'm helping to develop.
> However, you need to make sure you call DestroyJavaVM before creating the
> new instance. If any other non-daemon threads are still running when
> DestroyJavaVM is called, that call will block until they exit.
That's strange even when I kill the instance and let it sleep for a
few seconds
and restart it, it still crashes when creating a new instance. However
I can't test it
right now as the boot image dies during initialization. And we're not
spawning any threads
in the VM. This also happens only with the boot image! My first
thought was that you
overwrite the contents of the writeable part of the boot image and
thus it can't boot again.

> Anyway, the bottom line is you currently can't have more than one
> VM instance alive at a time, and you must make sure all your threads have
> exited or detached before you call DestroyJavaVM to ensure that the
> instance is really dead.
One instance is enough :) As long as we can shut it down and restart
it gracefully.
I also noticed that VM does not seem to clean up all objects properly,
that would be important
to us, as we need to clean up all native instances. Basically all
objects need to be able to finalize
even if the engine does not clean up manually.
But, right now, we're cleaning up manually, but the garbage collector
does not properly clean up it seems.
Even with a manual invocation of System.gc() I can't seem to get all
objects to shut down properly.

<snip>

Unfortunately I'm also able to crash the VM in interpreted mode. But
this time I've got a proper backtrace.
And I noticed that the VM does spawn it's own threads to perform gc/
deallocation, this is something
that should not happen. Even though we can handle these situations
gracefully as we retain objects we still need, this still
should not happen. We really need to be in full control over any
threading that happens.

Thread 1 is obviously the main thread, I've added annotations and you
can see how the engine calls into the VM
and the VM calls back into the native code.


THREAD 1
#8 …

// iterates over native child nodes

#9 0x0007a457 in Node::ThinkNode (this=0x24b7ec0, time=25.3669205,
deltaTime=0.044408001) at Node.cpp:93
#10 0x000793f9 in Node::ThinkChildren (this=0x2088ae0,
time=25.3669205, deltaTime=0.044408001) at Node.cpp:132
#11 0x0007a4f9 in Node::ThinkNode (this=0x2088ae0, time=25.3669205,
deltaTime=0.044408001) at Node.cpp:111
#12 0x000793f9 in Node::ThinkChildren (this=0x24b57f0,
time=25.3669205, deltaTime=0.044408001) at Node.cpp:132
#13 0x0007a4f9 in Node::ThinkNode (this=0x24b57f0, time=25.3669205,
deltaTime=0.044408001) at Node.cpp:111

// invoke native implantation of this node's native think method

#14 0x00174028 in Java_drahtkern_Game_NodeBase_ThinkNode
(env=0xab63004, unnamed_arg=0xbfffd12c, peer=38492144,
value1=25.3669205, value2=0.044408001) at NodeBase.cpp:214
#15 0x001e5045 in .Ltest () at x86.S:476
#16 0x001d061b in vm::dynamicCall (function=0x173fd0,
arguments=0xbfffd060, unnamed_arg=5, unnamed_arg=5, argumentsSize=24,
returnType=0) at x86.h:87
#17 0x001ce869 in call (this=0x24a4740, function=0x173fd0,
arguments=0xbfffd060, types=0xbfffd050 "\a\a\004\005\005p\031\001~\a
\177\016~\a\177\016\0040\266\n,\321\377\277\360WK\002", count=5,
size=24, returnType=0) at posix.cpp:749
#18 0x001d7ee7 in invokeNativeSlow (t=0xab63004, method=0xe7f0778,
function=0x173fd0) at interpret.cpp:600
#19 0x001d864b in invokeNative (t=0xab63004, method=0xe7f0778) at
interpret.cpp:658
#20 0x001e1ad3 in interpret3 (t=0xab63004, base=7) at interpret.cpp:
2664
#21 0x001d8855 in interpret2 (t=0xab63004, arguments=0xbfffde28) at
interpret.cpp:2697

// VM calls into native think method of node

#22 0x001e50d0 in vmRun () at x86.S:544
#23 0x00197db6 in vm::runRaw (t=0xab63004, function=0x1d8820
<interpret2>, arguments=0xbfffde28) at machine.h:1964
#24 0x00197e7b in vm::run (t=0xab63004, function=0x1d8820
<interpret2>, arguments=0xbfffde28) at machine.h:1971
#25 0x001d87be in interpret (t=0xab63004) at interpret.cpp:2711
#26 0x001d8ca2 in invoke (t=0xab63004, method=0xe7f3430) at
interpret.cpp:2859
#27 0x001d56ee in invokeList (this=0x24b6da4, vmt=0xab63004,
method=0xe7f3430, this_=0x0, indirectObjects=true,
arguments=0xbfffe15c "") at interpret.cpp:3032
#28 0x0019076b in callStaticVoidMethodV (t=0xab63004,
arguments=0xbfffe0f8) at jnienv.cpp:1147
#29 0x001e50d0 in vmRun () at x86.S:544
#30 0x00197db6 in vm::runRaw (t=0xab63004, function=0x1906e0
<callStaticVoidMethodV>, arguments=0xbfffe0f8) at machine.h:1964
#31 0x00197e7b in vm::run (t=0xab63004, function=0x1906e0
<callStaticVoidMethodV>, arguments=0xbfffe0f8) at machine.h:1971

#32 0x001906cb in CallStaticVoidMethodV (t=0xab63004,
unnamed_arg=0x24aa4e4, m=4, a=0xbfffe15c "") at jnienv.cpp:1157
#33 0x00170de2 in JNIEnv_::CallStaticVoidMethod (this=0xab63004,
cls=0x24aa4e4, methodID=0x4) at jni.h:1507

// executing VM think method

#34 0x00170015 in Scripting::Think (this=0xd846b0, time=25.3669205,
deltaTime=0.044408001) at Scripting.cpp:669
#35 0x0012bb55 in AppBase::ThinkFrame (this=0x2098650) at AppBase.cpp:
524

#51 0x000034b4 in main (argc=3, argv=0xbffff9bc) at main.m:12


THREAD 7
#0 0x93c4a9c6 in __pthread_kill ()
#1 0x9865af78 in pthread_kill ()
#2 0x9864bbdd in abort ()
#3 0x001cdfc5 in abort (this=0x24a4740) at posix.cpp:893
#4 0x00197f2c in vm::abort (s=0x24a4740) at system.h:170
#5 0x001980fd in vm::abort (t=0xab63004) at machine.h:1789
#6 0x001a0a83 in vm::enter (t=0xab63004, s=vm::Thread::ActiveState)
at machine.cpp:2874
#7 0x001979e1 in vm::StateResource::StateResource (this=0xb01019f0,
t=0xab63004, state=vm::Thread::ActiveState) at machine.h:1662
#8 0x00189d8c in DeleteGlobalRef (t=0xab63004, r=0xdc0094) at
jnienv.cpp:2207
#9 0x00189eb4 in DeleteWeakGlobalRef (t=0xab63004, r=0xdc0094) at
jnienv.cpp:2225
#10 0x0017274c in JNIEnv_::DeleteWeakGlobalRef (this=0xab63004,
ref=0xdc0094) at jni.h:1831
#11 0x001723da in ScriptObject::~ScriptObject (this=0xdc0080) at
ScriptObject.cpp:51
#12 0x000aa5c5 in Object::Release (this=0xdc0080) at Object.h:245
#13 0x00170cf7 in ScriptObject::Release (this=0xdc0080) at
ScriptObject.h:23
#14 0x00174a1a in ScriptableObject::ShutdownScriptObject
(this=0xdc0040) at ScriptableObject.cpp:50
#15 0x00174850 in Java_drahtkern_Common_ObjectBase_NativeRelease
(env=0x886c004, self=0x886c8c8, peer=14417984) at ObjectBase.cpp:20
#16 0x001e5045 in .Ltest () at x86.S:476
#17 0x001d061b in vm::dynamicCall (function=0x174820,
arguments=0xb0101c30, unnamed_arg=3, unnamed_arg=3, argumentsSize=16,
returnType=0) at x86.h:87
#18 0x001ce869 in call (this=0x24a4740, function=0x174820,
arguments=0xb0101c30, types=0xb0101c20 "\a\a\004\016\004\300\206\bx
\035\020\260\345z\035", count=3, size=16, returnType=0) at posix.cpp:
749
#19 0x001d7ee7 in invokeNativeSlow (t=0x886c004, method=0xe7eeef0,
function=0x174820) at interpret.cpp:600
#20 0x001d864b in invokeNative (t=0x886c004, method=0xe7eeef0) at
interpret.cpp:658
#21 0x001e1ad3 in interpret3 (t=0x886c004, base=1) at interpret.cpp:
2664
#22 0x001d8855 in interpret2 (t=0x886c004, arguments=0xb01029e8) at
interpret.cpp:2697
#23 0x001e50d0 in vmRun () at x86.S:544
#24 0x00197db6 in vm::runRaw (t=0x886c004, function=0x1d8820
<interpret2>, arguments=0xb01029e8) at machine.h:1964
#25 0x00197e7b in vm::run (t=0x886c004, function=0x1d8820
<interpret2>, arguments=0xb01029e8) at machine.h:1971
#26 0x001d87be in interpret (t=0x886c004) at interpret.cpp:2711
#27 0x001d8ca2 in invoke (t=0x886c004, method=0xe7eeec8) at
interpret.cpp:2859
#28 0x001d56ee in invokeList (this=0x24b6da4, vmt=0x886c004,
method=0xe7eeec8, this_=0xe7ffe04, indirectObjects=false,
arguments=0xb0102bc0 "\004\376\177\016\310\356~\016\350+
\020\260\340\177\031") at interpret.cpp:3032
#29 0x0017ec5d in vm::Processor::invoke (this=0x24b6da4, t=0x886c004,
method=0xe7eeec8, this_=0xe7ffe04) at processor.h:163
#30 0x0019fe4d in invoke (t=0x886c004, arguments=0xb0102d00) at
machine.cpp:645
#31 0x001e50d0 in vmRun () at x86.S:544
#32 0x00197db6 in vm::runRaw (t=0x886c004, function=0x19fe00 <invoke>,
arguments=0xb0102d00) at machine.h:1964
#33 0x00197e7b in vm::run (t=0x886c004, function=0x19fe00 <invoke>,
arguments=0xb0102d00) at machine.h:1971
#34 0x001a77d6 in finalizeObject (t=0x886c004, o=0xe7ffe04,
name=0x40dd4d "finalize") at machine.cpp:668
#35 0x001a7bf0 in vm::runFinalizeThread (t=0x886c004) at machine.cpp:
4304
#36 0x001cd7a4 in vm::runThread (t=0x886c004, unnamed_arg=0x0) at
machine.h:1991
#37 0x001e50d0 in vmRun () at x86.S:544
#38 0x00197db6 in vm::runRaw (t=0x886c004, function=0x1cd740
<vm::runThread(vm::Thread*, unsigned long*)>, arguments=0x0) at
machine.h:1964
#39 0x00197e7b in vm::run (t=0x886c004, function=0x1cd740
<vm::runThread(vm::Thread*, unsigned long*)>, arguments=0x0) at
machine.h:1971
#40 0x001c66c7 in vm::Thread::Runnable::run (this=0x886c04c) at
machine.h:1525
#41 0x001cd91f in run (r=0x886c04c) at posix.cpp:95
#42 0x98658ed9 in _pthread_start ()
#43 0x9865c6de in thread_start ()

Joel Dice

unread,
Mar 3, 2012, 11:10:53 AM3/3/12
to Avian
On Sat, 3 Mar 2012, Daniel Jaeger wrote:

>> Thanks, that helps me understand the problem better. Now, if you're
>> willing and able to provide a concrete example of this workload which I
>> can build and profile, I'll be happy to help optimize it. What I'm trying
>> to avoid is micro-optimization without measurement and feedback from a
>> realistic test case. If I try to write such a test case based on what
>> you've told me, the result might not emulate your workload, whereas you
>> know how to get it right the first time. No pressure, but it would make
>> it much easier for me to provide useful advice and improvements.
> Unfortunately I can't provide you with our source code, but I could
> probably
> send you a binary build of the engine. Maybe you can inject/hook the c
> api creation
> of our built-in vm and simply inject a call to create your vm with
> code you can debug/profile.
> This engine branch currently builds on OSX, but I can probably setup a
> win32 build for you.

Anything but Windows! :) Linux or OSX would be fine, but what I'm really
looking for is the source for a representative test case, something that
does the same things the performance-sensitive path of your engine does.
I don't need access to the engine itself, just something that behaves like
it well enough to profile and optimize.

>
>> Yes, "ENTER(t, Thread::ExclusiveState)" will ensure you're the only
>> VM-attached thread running, but that's expensive and unnecessary for your
>> case.
> That's good, I can't stress how important it is that we've got full
> control
> of the VM and it's threading. Is there another way to really halt all
> vm threads
> i.e. if the user creates a Thread instance in a java class, that
> thread should also be halted.

Remember that these are native threads which may be executing either
native or Java code at any given instant. Such threads may hold arbitrary
resources like open file descriptors, mutexes, sockets, etc., all of which
would be leaked or unreleased if we just killed the threads outright, and
I wouldn't even know how to do that with pthreads. That's why
Thread.stop() is deprecated -- there's no safe way to implement it.

That means you really need your application threads to be well behaved
such that they respond to a request for an orderly shutdown, and you need
to block until they've all finished responding to the request.

> See below, I already noticed the VM spawns threads on it's own.

Yes, but you have some control over that, as I will explain.

>> No, the field offsets never change for a given class. In fact, we
>> calculate them ahead of time for classes the VM uses internally, so
>> they're compile-time constants. Thus, a call to e.g. vm::stringData(t,
>> myString) is no more expensive after inlining and optimization than a C
>> struct field dereference through a pointer, e.g. myString->data. If you
>> need every last bit of performance, you may be able to use the type
>> generator (type-generator.cpp) to generate such inlineable functions for
>> your application, too.
> That's good to know, that opens up a lot of room for performance
> improvements!
> Another thing that would be great if it's possible to do the same for
> java code in the vm, i.e.
> direct access to compile-time pre-mapped fields in native C structs.

There are two ways to do that in Java: Direct*Buffers and sun.misc.Unsafe.
Normally the latter is only available to privileged standard library code,
but Avian won't prevent application code from accessing it. Anyway, the
Unsafe APIs will be most convenient for what you're doing, and they'll
also be easier to implement in Avian than the Direct*Buffer stuff.
Implementing it efficiently means teaching the compiler to recognize e.g.
Unsafe.getBoolean and emit an intrinsic instead of a native call for it.
We already use intrinsics for e.g. Math.sqrt, which the compiler can
simplify to a single instruction if the processor architecture supports
it. All the infrastructure is in place, so adding the Unsafe methods
should be pretty easy.

>>> we're seeing crashes when using the boot image on the workstation and
>>> on the device when calling from java to native and back to java. It
>>> doesn't happen every time and it's hard to trace.
>> Can you post a backtrace from GDB or LLDB?
> Unfortunately not a useful one, the engine dies hard and gdb returns
> "can't unwind past frame"
> However I've already traced it manually, the last thing the engine
> does is call the native alloc method in the constructor of a java
> object.
> The VM seems to not be able to return back into the VM. Again, this
> only happens with the boot image!

Is it getting a SIGBUS or SIGSEGV, or something else?

That would indicate that it's receiving a signal while executing Java
code, since you're inside the code image. The only signals Java code
should ever generate are SIGBUS or SIGSEGV, which are turned into
NullPoitnerExceptions by the VM signal handler, or SIGFPE, which is
turned into an ArithmeticException.

Of course, when running in a debugger, the debugger will intercept the
signal before the VM has a chance to process it. In GDB, you can usually
suppress that by issuing e.g. "handle SIGSEGV nostop noprint pass", but in
my experience that doesn't work for all signal types in Apple's version of
GDB, so I have to resort to either detaching the debugger when it gets
such a signal or not even running it in the debugger in the first place.

> #1 0x00000021 in ?? ()
> <<<
>
> All other threads contain the same bt:
>>>>
> (gdb) bt
> #0 0x93c4b02e in __workq_kernreturn ()
> #1 0x9865accf in _pthread_wqthread ()
> #2 0x9865c6fe in start_wqthread ()
> <<<
>
> This is the corresponding java code:
>>>>
> public static void Initialize()
> {
> Console.WriteLine("App initialized");
>
> _main = new Node();
>
> Console.WriteLine("main node allocated");
>
> // create a 3d perspective camera node
> // the CameraNode origin + rotation define the origin and rotation
> of
> // the camera
> _scene = new CameraNode();

> � crash

My first guess is that NativeAlloc is somehow clobbering memory such that
the "this" reference or _transformation reference becomes corrupted. It
might help to disassemble the code surrounding the address where it
receives the signal and try to match that up with the original Java
bytecode. I'd be happy to help if you can provide a test case, but I
don't know if that's practical for you.

>
>>> I also noticed that restarting the VM (killing and creating a new
>>> instance) is impossible when using a boot image.
>> It's not impossible. We're doing it in an app I'm helping to develop.
>> However, you need to make sure you call DestroyJavaVM before creating the
>> new instance. If any other non-daemon threads are still running when
>> DestroyJavaVM is called, that call will block until they exit.
> That's strange even when I kill the instance and let it sleep for a
> few seconds
> and restart it, it still crashes when creating a new instance. However
> I can't test it
> right now as the boot image dies during initialization. And we're not
> spawning any threads
> in the VM. This also happens only with the boot image! My first
> thought was that you
> overwrite the contents of the writeable part of the boot image and
> thus it can't boot again.

Yes, that was originally a problem, but now (as of a few months ago)
there's code to ensure that the VM instance leaves the boot image in a
known good state before exiting so that the next instance can use it. If
you're not spawning any threads, I can't guess why it's not working. As
always, if you can provide a test case, I'll look into it.

>
>> Anyway, the bottom line is you currently can't have more than one
>> VM instance alive at a time, and you must make sure all your threads have
>> exited or detached before you call DestroyJavaVM to ensure that the
>> instance is really dead.
> One instance is enough :) As long as we can shut it down and restart
> it gracefully.
> I also noticed that VM does not seem to clean up all objects properly,
> that would be important
> to us, as we need to clean up all native instances. Basically all
> objects need to be able to finalize
> even if the engine does not clean up manually.
> But, right now, we're cleaning up manually, but the garbage collector
> does not properly clean up it seems.
> Even with a manual invocation of System.gc() I can't seem to get all
> objects to shut down properly.

What do you mean by getting "objects to shut down"? Do you mean all the
objects with non-trivial finalize methods are finalized? That happens in
the turnOffTheLights function in machine.cpp, but that will only run when
the last thread in the VM exits, and based on the problem you're having
above it sounds like that's not happening for some reason. Of course, if
a finalize method itself blocks forever, the VM will be unable to finish
cleaning up.

> <snip>
>
> Unfortunately I'm also able to crash the VM in interpreted mode. But
> this time I've got a proper backtrace.
> And I noticed that the VM does spawn it's own threads to perform gc/
> deallocation, this is something
> that should not happen.

No threads are spawned to perform GC. The only thread the VM ever spawns
is the finalize thread, and that's because the Java spec guarantees that
finalization happens in a non-application thread. In Avian, that thread
is only created lazily the first time an object with a non-trivial
finalize method is allocated. The Avian class library does not use
finalize anywhere, so only application code can trigger it. The upshot is
that if you don't want that thread to be spawned, you should not use
finalize; use phantom references instead.

I should have asked this earlier: are you using Avian's class library or
OpenJDK's?

> Even though we can handle these situations
> gracefully as we retain objects we still need, this still
> should not happen. We really need to be in full control over any
> threading that happens.

We could add a system property, e.g. avian.no.finalize.thread, which tells
the VM not to spawn a finalize thread even if objects are allocated which
would otherwise demand it, but then those objects simply won't be
finalized when they become unreachable.

> Thread 1 is obviously the main thread, I've added annotations and you
> can see how the engine calls into the VM
> and the VM calls back into the native code.
>
>
> THREAD 1

> #8 �

> �


> #51 0x000034b4 in main (argc=3, argv=0xbffff9bc) at main.m:12
>
>
> THREAD 7
> #0 0x93c4a9c6 in __pthread_kill ()
> #1 0x9865af78 in pthread_kill ()
> #2 0x9864bbdd in abort ()
> #3 0x001cdfc5 in abort (this=0x24a4740) at posix.cpp:893
> #4 0x00197f2c in vm::abort (s=0x24a4740) at system.h:170
> #5 0x001980fd in vm::abort (t=0xab63004) at machine.h:1789
> #6 0x001a0a83 in vm::enter (t=0xab63004, s=vm::Thread::ActiveState)
> at machine.cpp:2874
> #7 0x001979e1 in vm::StateResource::StateResource (this=0xb01019f0,
> t=0xab63004, state=vm::Thread::ActiveState) at machine.h:1662
> #8 0x00189d8c in DeleteGlobalRef (t=0xab63004, r=0xdc0094) at
> jnienv.cpp:2207

My line numbers are not matching up with what I'm seeing in this trace.
For example, jnienv.cpp:2207 is in NewWeakGlobalRef, not DeleteGlobalRef,
when I look at the latest code from the master branch of avian.git. Are
you using the latest code? Have you patched it? I'd like to help here,
but I'm having trouble following along due to the line number mismatch.

Joel Dice

unread,
Mar 3, 2012, 11:52:05 AM3/3/12
to Joel Dice, Avian
On Sat, 3 Mar 2012, Joel Dice wrote:

>> THREAD 7
>> #0 0x93c4a9c6 in __pthread_kill ()
>> #1 0x9865af78 in pthread_kill ()
>> #2 0x9864bbdd in abort ()
>> #3 0x001cdfc5 in abort (this=0x24a4740) at posix.cpp:893
>> #4 0x00197f2c in vm::abort (s=0x24a4740) at system.h:170
>> #5 0x001980fd in vm::abort (t=0xab63004) at machine.h:1789
>> #6 0x001a0a83 in vm::enter (t=0xab63004, s=vm::Thread::ActiveState)
>> at machine.cpp:2874
>> #7 0x001979e1 in vm::StateResource::StateResource (this=0xb01019f0,
>> t=0xab63004, state=vm::Thread::ActiveState) at machine.h:1662
>> #8 0x00189d8c in DeleteGlobalRef (t=0xab63004, r=0xdc0094) at
>> jnienv.cpp:2207
>
> My line numbers are not matching up with what I'm seeing in this trace. For
> example, jnienv.cpp:2207 is in NewWeakGlobalRef, not DeleteGlobalRef, when I
> look at the latest code from the master branch of avian.git. Are you using
> the latest code? Have you patched it? I'd like to help here, but I'm having
> trouble following along due to the line number mismatch.

By the way, you aren't by any chance storing a JNIEnv* in ScriptObject are
you? That would explain the crash. JNIEnv* is thread-specific, so if you
try to use in from different thread (the finalize thread, in this case),
bad things will happen. You need to always either use the JNIEnv* passed
to the latest native method call in the current thread or use
JavaVM::GetEnv to get it.

Daniel Jaeger

unread,
Mar 3, 2012, 11:56:49 AM3/3/12
to Avian
>By the way, you aren't by any chance storing a JNIEnv* in ScriptObject are you? That would explain the crash.
> JNIEnv* is thread-specific, so if you try to use in from different thread (the finalize thread, in this case), bad > things will happen.
> You need to always either use the JNIEnv* passed to the latest native method call in the current thread or use JavaVM::GetEnv to get it.
Great observation! Yes indeed, the JNIEnv* is stored when the vm-
instance is allocated. Albeit not in the ScriptObject but in the
Scripting:: class. It's basically
the wrapper for the JNIEnv the engine uses.
I'll see if this fixes the crashes and revert back to you, I'll also
reply to your other post in a minute.

Daniel Jaeger

unread,
Mar 3, 2012, 12:21:12 PM3/3/12
to Avian
Just looked at GetEnv:
*t = static_cast<Thread*>(m->localThread->get());
That's embarrassing, I should have looked at what function I'm
calling :)

Am I right in the assumption that a thread local environment is not
necessary for all fields? For instance GetField* SetField* lookups can
work with the
environment created when the VM was created.
I'm still changing the code to always grab the thread local
environment, I'll let you know as soon as I have tested it with the
fix.

But we still need to prevent the finalize thread, we can call it
manually at the end of the frame, or if we've got frame time overhead
(i.e. the computer renders faster than 60fps)

Daniel Jaeger

unread,
Mar 3, 2012, 1:08:59 PM3/3/12
to Avian
Great, that seemed to fix the random crashes! Thanks a lot!
However, the boot image build still crashes directly after the
CameraNode ctor.
Even when commenting out the native function body and not allocating
any objects at all, and returning 0.
I also implemented the native allocator with the Avian calling
convention and an empty body, but that also leads to the crash.

resolved=Avian_drahtkern_Game_Node_NativeAlloc at=0x176360
resolved=Java_java_lang_System_currentTimeMillis at=0x1680b0
resolved=Avian_java_lang_System_arraycopy at=0x17cd50
resolved=Java_java_lang_System_getProperty at=0x168450
resolved=Java_java_io_FileOutputStream_write__I_3BII at=0x166590
call to super (NodeBase())
resolved=Avian_drahtkern_Game_CameraNode_NativeAlloc at=0x1763a0
native alloc done
Previous frame inner to this frame (gdb could not unwind past this
frame)
(gdb) bt
#0 0x002e9cdc in _binary_codeimage_bin_start ()
#1 0x00000021 in ?? ()
LLDB:
(lldb) bt
* thread #1: tid = 0x2803, 0x002e9cdc zombies-
x`_binary_codeimage_bin_start + 293308, stop reason = EXC_BAD_ACCESS
(code=2, address=0x4)
frame #0: 0x002e9cdc zombies-x`_binary_codeimage_bin_start +
293308

on an iOS device it seems to crash at exactly the same position.

call to super (NodeBase())
native alloc done
(lldb) bt
* thread #1: tid = 0x1c03, 0x00281914
zombies`_binary_codeimage_bin_start + 340508, stop reason =
EXC_BAD_ACCESS (code=1, address=0x4)
frame #0: 0x00281914 zombies`_binary_codeimage_bin_start + 340508

Joel Dice

unread,
Mar 3, 2012, 2:01:31 PM3/3/12
to Avian
On Sat, 3 Mar 2012, Daniel Jaeger wrote:

> Just looked at GetEnv:
> *t = static_cast<Thread*>(m->localThread->get());
> That's embarrassing, I should have looked at what function I'm
> calling :)
>
> Am I right in the assumption that a thread local environment is not
> necessary for all fields? For instance GetField* SetField* lookups can
> work with the
> environment created when the VM was created.

No, because those methods still need to do a thread state transtion from
idle to active and back again. You'll get random crashes depending on
what the thread that really owns that JNIEnv* happens to be doing at the
time.

If you're trying to avoid a thread local lookup, just pass the JNIEnv*
straight through from your native method to whatever functions need it.

> I'm still changing the code to always grab the thread local
> environment, I'll let you know as soon as I have tested it with the
> fix.
>
> But we still need to prevent the finalize thread, we can call it
> manually at the end of the frame, or if we've got frame time overhead
> (i.e. the computer renders faster than 60fps)

That's why phantom references exist -- you get control over when resources
owned by collected objects are released. Here are your options in order
of most control to least control:

1. Explicitly release native resources associated with a given object
when you're done using it (which means before it becomes unreachable,
of course). That's what SWT and many other successful libraries do.
I strongly recommend it.

2. Use phantom references to release native resources after the object
which own them become unreachable. The drawback here, and with option
3 below, is that when and why GC occurs is non-deterministic from the
point of view of the application, and the amount of native resources
in use is not and cannot be considered by the VM when it decides
whether or not to GC. All it sees is Java heap memory pressure; it
knows nothing about natively-allocated memory or other limited OS
resources attached to Java objects, so it may not GC often enough to
free them up.

3. Use Object.finalize(). This sucks. I consider it to be the absolute
worst "feature" of Java.

>
> On Mar 3, 5:56�pm, Daniel Jaeger <dwckongreg...@googlemail.com> wrote:
>>> By the way, you aren't by any chance storing a JNIEnv* in ScriptObject are you? �That would explain the crash.
>>> JNIEnv* is thread-specific, so if you try to use in from different thread (the finalize thread, in this case), bad > things will happen.
>>> You need to always either use the JNIEnv* passed to the latest native method call in the current thread or use JavaVM::GetEnv to get it.
>>
>> Great observation! Yes indeed, the JNIEnv* is stored when the vm-
>> instance is allocated. Albeit not in the ScriptObject but in the
>> Scripting:: class. It's basically
>> the wrapper for the JNIEnv the engine uses.
>> I'll see if this fixes the crashes and revert back to you, I'll also
>> reply to your other post in a minute.
>

Joel Dice

unread,
Mar 3, 2012, 2:09:58 PM3/3/12
to Avian

Based on the code you posted earlier, this looks like a simple null
pointer dereference. I'd need to see the disassembly, but I'm guessing
we're seeing address=0x4 because we're trying to do a bounds check on an
array (the length of the array is at a four byte offset from the object
start on a 32-bit platform), but the array reference is null. If you ran
this outside a debugger, I'm guessing you'd get a NullPointerException.
I recommend adding "if (_transformation == null) throw new
NullPointerException();" before the array dereference and, and then work
backwards, moving it earlier and earlier in the code to find out when it's
becoming null.

Daniel Jaeger

unread,
Mar 3, 2012, 4:33:44 PM3/3/12
to Avian
You were right again :)

call to super (NodeBase())
resolved=Java_drahtkern_Game_CameraNode_NativeAlloc at=0x1762a0
native alloc done
resolved=Avian_java_lang_Throwable_trace at=0x17abb0
java/lang/NullPointerException: _transformation == null (post native
call)
at drahtkern/Game/CameraNode.<init> (line 37)
at AppBase.Initialize (line 39)

Right after the native call the _transformation array becomes NULL.
It's important to note that the NativeAlloc method still contains an
empty body, so it's just a simple invocation that does nothing.

Daniel Jaeger

unread,
Mar 3, 2012, 4:45:39 PM3/3/12
to Avian
I also noticed that on some rare occasions the interpreter can't seem
to pass floats to the vm.
This happens right after initializing the instance, and it stays that
way. Exiting the app and restarting and it's working fine again.
Maybe this is related, we're using a built-in Jikes compiler to
compile java code on the fly. Haven't noticed any problems in compiled
mode so far.

WARNING: Could not load material=models/primitives/cubetexture.png
even though it's referenced. Trying to update cache.
...10 materials in memory
...10 textures on gpu
WARNING: delayed loading was initiated because of material=models/
primitives/cubetexture.png
WARNING: excessive frame alloc=5854.27 kb
time=3.68935e+19 spawntime=3.68935e+19
time=3.68935e+19 spawntime=3.68935e+19
time=3.68935e+19 spawntime=3.68935e+19


On Mar 3, 10:33 pm, Daniel Jaeger <dwckongreg...@googlemail.com>
wrote:

Daniel Jaeger

unread,
Mar 3, 2012, 4:55:47 PM3/3/12
to Avian
I guess that was too fast, just happened in compiled mode. Restarted
the same build and floats are working again.
Is it possible that an attached debugger can cause this?

On Mar 3, 10:45 pm, Daniel Jaeger <dwckongreg...@googlemail.com>

Joel Dice

unread,
Mar 3, 2012, 6:56:24 PM3/3/12
to Avian
On Sat, 3 Mar 2012, Daniel Jaeger wrote:

> You were right again :)
>
> call to super (NodeBase())
> resolved=Java_drahtkern_Game_CameraNode_NativeAlloc at=0x1762a0
> native alloc done
> resolved=Avian_java_lang_Throwable_trace at=0x17abb0
> java/lang/NullPointerException: _transformation == null (post native
> call)
> at drahtkern/Game/CameraNode.<init> (line 37)
> at AppBase.Initialize (line 39)
>
> Right after the native call the _transformation array becomes NULL.
> It's important to note that the NativeAlloc method still contains an
> empty body, so it's just a simple invocation that does nothing.

The original code you sent looked like this:

_peer = NativeAlloc();
System.out.println("native alloc done");

_transformation[3] = -90;

What happens if you change that first line to "/*_peer =*/
NativeAlloc();"? What happens when you change it to "_peer = 0;"? I'm
wondering if writing to the _peer reference is somehow clobbering
the _transformation reference. It could be that there's a bug in field
offset calculation in the boot image generator that would cause that.

As always, if you can reduce this to a test case that I can run myself, it
will speed the process up considerably. Although we're clearly having
good luck so far with my random guessing and debugging-by-proxy :)

Joel Dice

unread,
Mar 3, 2012, 7:07:20 PM3/3/12
to Avian
On Sat, 3 Mar 2012, Daniel Jaeger wrote:

> I guess that was too fast, just happened in compiled mode. Restarted
> the same build and floats are working again.
> Is it possible that an attached debugger can cause this?

I wouldn't think so. When you see you're passing floats to the VM, do you
mean as arguments to Call*Method, SetFloatField, SetFloatArrayRegion, or
something else?

It does sound similar to the bug we fixed with float argument passing the
other day, but I don't know why it would now sometimes work and sometimes
not. Is it possible you have an old build cached somewhere that's being
used when debugging but not when running outside the debugger? When I do
iOS development, I'm in the habit of clearing out
~/Library/Developer/Xcode/DerivedData/* and ~/Library/Application
Support/iPhone Simulator/*/Applications/* before every build to avoid such
confusion.

Daniel Jaeger

unread,
Mar 4, 2012, 8:44:24 AM3/4/12
to Avian
> What happens when you change it to "_peer = 0;"?
> I'm wondering if writing to the _peer reference is somehow clobbering the _transformation reference.
> It could be that there's a bug in field offset calculation in the boot image generator that would cause that.

You were right again :)! - I wouldn't have thought that a simple
assignment can mess up the class tables.


> I wouldn't think so. When you see you're passing floats to the VM,
> do you mean as arguments to Call*Method, SetFloatField, SetFloatArrayRegion, or something else?

I'm using CallVoidMethod(...)
Haven't seen any issues with the SetField/ArrayRegion methods.

> It does sound similar to the bug we fixed with float argument passing the other day, but I don't know why it would now sometimes work and sometimes
> not. Is it possible you have an old build cached somewhere that's being used when debugging but not when running outside the debugger? When I do iOS
> development, I'm in the habit of clearing out ~/Library/Developer/Xcode/DerivedData/* and ~/Library/Application Support/iPhone Simulator/*
> /Applications/* before every build to avoid such confusion.

Currently avian is being built with an Xcode project that's a direct
dependency of the engine, so the avian lib will always be rebuilt
whenever we rebuild the project. We've got a lot of these dependencies
in the engine and we've never had problems with outdated libraries
being linked in.
However, I have ensured that I have not messed up avian with my custom
build setup and modified image generator (added armv7, supports
baseclasspath/jar + additional classpath and directly generates .o
files, supports armv6+armv7 fat/universal binaries).
This kind of setup is mandatory for the project to keep the deployment
process simple. Building for release will automatically rebuild avian,
build a universal bootimage and link it in. Works great.


> As always, if you can reduce this to a test case that I can run myself, it will speed the process up considerably. Although we're clearly having good
> luck so far with my random guessing and debugging-by-proxy :)

I have managed to reproduce the issue with your build setup. I'll send
you the sources directly as they're not intended for public release.


Here goes the reply to your other post:

> Anything but Windows! :) Linux or OSX would be fine, but what I'm really looking for is the source for a representative test case,
> something that does the same things the performance-sensitive path of your engine does.
> I don't need access to the engine itself, just something that behaves like it well enough to profile and optimize.
Building a representative test case without the engine will be really
difficult I guess.
But if you can work with the engine I've reassured that it's ok to
send you a stripped down build.
Anyways, for the time being I've setup that test case I've mentioned
earlier.

> Remember that these are native threads which may be executing either native or Java code at any given instant. Such threads may
> hold arbitrary resources like open file descriptors, mutexes, sockets, etc., all of which would be leaked or unreleased if we
> just killed the threads outright, and I wouldn't even know how to do that with pthreads. That's why Thread.stop() is deprecated
> -- there's no safe way to implement it.
> That means you really need your application threads to be well behaved such that they respond to a request for an orderly
> shutdown, and you need to block until they've all finished responding to the request.

No I wasn't talking about killing threads, my idea was to halt them -
probably using a wait condition: http://linux.die.net/man/3/pthread_cond_wait
But I'm not sure if that's even possible as I don't know how java
threads are being handle in the vm.

>There are two ways to do that in Java: Direct*Buffers and sun.misc.Unsafe.
>Normally the latter is only available to privileged standard library code, but Avian won't prevent application code from accessing
>it. Anyway, the Unsafe APIs will be most convenient for what you're doing, and they'll also be easier to implement in Avian than
>the Direct*Buffer stuff. Implementing it efficiently means teaching the compiler to recognize e.g. Unsafe.getBoolean and emit an
>intrinsic instead of a native call for it. We already use intrinsics for e.g. Math.sqrt, which the compiler can simplify to a
>single instruction if the processor architecture supports it. All the infrastructure is in place, so adding the Unsafe methods
>should be pretty easy.
Great, implementing the Unsafe.get* methods should be the way to go
then. I'll take a look at how Math.sqrt intrinstics are implemented -
once I get Avian to run stable - that's most important.

> My first guess is that NativeAlloc is somehow clobbering memory such that the "this" reference or _transformation reference
> becomes corrupted. It might help to disassemble the code surrounding the address where it receives the signal and try to match
> that up with the original Java bytecode. I'd be happy to help if you can provide a test case, but I don't know if that's
> practical for you.
It was the assignment to the long _peer value, I'll send you the test
case!


> Yes, that was originally a problem, but now (as of a few months ago) there's code to ensure that the VM instance leaves the boot
> image in a known good state before exiting so that the next instance can use it. If you're not spawning any threads, I can't
> guess why it's not working. As always, if you can provide a test case, I'll look into it.
Once we get the bootimage working stable I'll try to produce a test
case for this.

> What do you mean by getting "objects to shut down"?
> Do you mean all the objects with non-trivial finalize methods are finalized?
Exactly. The objects don't seem to get their finalize() method
invoked.

> That happens in the turnOffTheLights function in machine.cpp, but that will only run when the last thread in the VM exits,
> and based on the problem you're having above it sounds like that's not happening for some reason. Of course, if a finalize
> method itself blocks forever, the VM will be unable to finish cleaning up.
The finalize methods should not be blocking, they're only invoking a
"Release" on the native object.
And we're not spawning any additional threads that could stop the VM
from shutting down.

> No threads are spawned to perform GC. The only thread the VM ever spawns is the finalize thread, and that's because the Java
> spec guarantees that finalization happens in a non-application thread. In Avian, that thread is only created lazily the first
> time an object with a non-trivial finalize method is allocated. The Avian class library does not use finalize anywhere, so only
> application code can trigger it. The upshot is that if you don't want that thread to be spawned, you should not use finalize;
> use phantom references instead.
We don't mind breaking the Java spec as long as it suits our needs. So
we can move the finalize thread onto the main thread and invoke
finalize whenever we want to.

> I should have asked this earlier: are you using Avian's class library or OpenJDK's?
-Avian


> We could add a system property, e.g. avian.no.finalize.thread, which tells the VM not to spawn a finalize thread even if objects > are allocated which would otherwise demand it, but then those objects simply won't be finalized when they become unreachable.
That's ok as long as we can then invoke the (blocking) cleanup process
manually on a thread we choose.

> My line numbers are not matching up with what I'm seeing in this trace. For example, jnienv.cpp:2207 is in NewWeakGlobalRef,
> not DeleteGlobalRef, when I look at the latest code from the master branch of avian.git. Are you using the latest code?
> Have you patched it? I'd like to help here, but I'm having trouble following along due to the line number mismatch.
Yes, the codebase is the latest, I have pulled in all changes from
your git. However I had to change a couple of #define/#include lines
here and there to integrate Avian into our build process. I also tend
to reindent the code to match our style guides, whenever I'm studying
the code. Symbolnames should match up just fine though.
But to not waste your time I have already tried it with an unmodified
Avian build, built with your make files and I'm seing the same
behavior. I'll send you the testcase-src to the bootimage null-ptr
crash in a few minutes, it's integrated into the hello-ios sample.




Joel Dice

unread,
Mar 4, 2012, 2:11:32 PM3/4/12
to Avian
On Sun, 4 Mar 2012, Daniel Jaeger wrote:

>> What happens when you change it to "_peer = 0;"?
>> I'm wondering if writing to the _peer reference is somehow clobbering the _transformation reference.
>> It could be that there's a bug in field offset calculation in the boot image generator that would cause that.
>
> You were right again :)! - I wouldn't have thought that a simple
> assignment can mess up the class tables.

Please try pulling the latest from the Git repo and let me know how it
works.

http://oss.readytalk.com/gitweb?p=avian.git;a=commitdiff;h=ac63d084505cd14cbb2828a3fd93207d6a7eaf1b

>
>> I wouldn't think so. When you see you're passing floats to the VM,
>> do you mean as arguments to Call*Method, SetFloatField, SetFloatArrayRegion, or something else?
>
> I'm using CallVoidMethod(...)
> Haven't seen any issues with the SetField/ArrayRegion methods.
>
>> It does sound similar to the bug we fixed with float argument passing the other day, but I don't know why it would now sometimes work and sometimes
>> not. Is it possible you have an old build cached somewhere that's being used when debugging but not when running outside the debugger? When I do iOS
>> development, I'm in the habit of clearing out ~/Library/Developer/Xcode/DerivedData/* and ~/Library/Application Support/iPhone Simulator/*
>> /Applications/* before every build to avoid such confusion.
>
> Currently avian is being built with an Xcode project that's a direct
> dependency of the engine, so the avian lib will always be rebuilt
> whenever we rebuild the project. We've got a lot of these dependencies
> in the engine and we've never had problems with outdated libraries
> being linked in.
> However, I have ensured that I have not messed up avian with my custom
> build setup and modified image generator (added armv7, supports
> baseclasspath/jar + additional classpath and directly generates .o
> files, supports armv6+armv7 fat/universal binaries).
> This kind of setup is mandatory for the project to keep the deployment
> process simple. Building for release will automatically rebuild avian,
> build a universal bootimage and link it in. Works great.

That sounds reasonable. I'm afraid I don't have any more guesses about
the float argument passing problem. I can't imagine what would cause it
to work most of the time but occasionally stop working. I presume you've
fixed up all the incorrect uses of JNIEnv*, right?

> Here goes the reply to your other post:
>
>> Anything but Windows! :) Linux or OSX would be fine, but what I'm really looking for is the source for a representative test case,
>> something that does the same things the performance-sensitive path of your engine does.
>> I don't need access to the engine itself, just something that behaves like it well enough to profile and optimize.
> Building a representative test case without the engine will be really
> difficult I guess.
> But if you can work with the engine I've reassured that it's ok to
> send you a stripped down build.
> Anyways, for the time being I've setup that test case I've mentioned
> earlier.

Is that test case for debugging the nulled-out _transformation field, the
float argument passing problem, or for performance profiling? Or all
three?

Anyway, I haven't actually tried running it yet, since I don't have access
to a Mac right now.

>
>> Remember that these are native threads which may be executing either native or Java code at any given instant. Such threads may
>> hold arbitrary resources like open file descriptors, mutexes, sockets, etc., all of which would be leaked or unreleased if we
>> just killed the threads outright, and I wouldn't even know how to do that with pthreads. That's why Thread.stop() is deprecated
>> -- there's no safe way to implement it.
>> That means you really need your application threads to be well behaved such that they respond to a request for an orderly
>> shutdown, and you need to block until they've all finished responding to the request.
>
> No I wasn't talking about killing threads, my idea was to halt them -
> probably using a wait condition: http://linux.die.net/man/3/pthread_cond_wait
> But I'm not sure if that's even possible as I don't know how java
> threads are being handle in the vm.

They're just native pthreads (or Windows threads), with all the advantages
and limitations that implies. Anything you can do with a pthread, you can
do with a thread attached to the VM, basically.

I've lost track of what you're trying to accomplish here. I thought this
came up because you wanted to be able to access Java fields from native
code without worrying about a GC being initiated from another thread and
moving the object out from under you. If that's all you're worried about,
there's no need to pause all the other threads, just enter
Thread::ActiveState if you haven't already. Is the some other reason you
need all the other threads paused?

>> No threads are spawned to perform GC. The only thread the VM ever spawns is the finalize thread, and that's because the Java
>> spec guarantees that finalization happens in a non-application thread. In Avian, that thread is only created lazily the first
>> time an object with a non-trivial finalize method is allocated. The Avian class library does not use finalize anywhere, so only
>> application code can trigger it. The upshot is that if you don't want that thread to be spawned, you should not use finalize;
>> use phantom references instead.
> We don't mind breaking the Java spec as long as it suits our needs. So
> we can move the finalize thread onto the main thread and invoke
> finalize whenever we want to.

My point is that there's already an official, well-defined way to do this
in Java: phantom references. No need to hack the VM, and your application
code remains portable across VMs.

Daniel Jaeger

unread,
Mar 6, 2012, 9:53:42 AM3/6/12
to Avian
> Please try pulling the latest from the Git repo and let me know how it
> works.
The latest commit fixed the issue with the crashing boot image.
Thanks.

> That sounds reasonable. I'm afraid I don't have any more guesses about
> the float argument passing problem. I can't imagine what would cause it
> to work most of the time but occasionally stop working. I presume you've
> fixed up all the incorrect uses of JNIEnv*, right?
Yes, the only way the engine can access the JNIEnv* is either by pass-
through
or by invoking MakeEnvironment() which grabs the current local thread.
I already mentioned it, maybe it's related to the Jikes compiler which
is
built into the engine to dynamically recompile classes. I'm going to
disable
it and switch to javac to check if I can produce the error again.

> Is that test case for debugging the nulled-out _transformation field, the
> float argument passing problem, or for performance profiling? Or all
> three?
That test-case was only for the nulled-out _transformation field.
So you can throw it away now :)

> I've lost track of what you're trying to accomplish here. I thought this
> came up because you wanted to be able to access Java fields from native
> code without worrying about a GC being initiated from another thread and
> moving the object out from under you. If that's all you're worried about,
> there's no need to pause all the other threads, just enter
> Thread::ActiveState if you haven't already. Is the some other reason you
> need all the other threads paused?
The engine is not thread safe for performance reasons so it would be
great to be in control over what happens for various reasons.


> My point is that there's already an official, well-defined way to do this
> in Java: phantom references. No need to hack the VM, and your application
> code remains portable across VMs.
That is true, but our code will not be easily portable anyway as there
will be heavy
use of the Avian_ calling convention.
While I know that we can close our objects early on by calling a close
method similar
to SWT I want to ensure that it's impossible to break the engine on
the native
side through java. That basically means disabling multi threading.

<snip>

In the meantime I have added instrinsic support for unsafe address
access.
It seems to work, however I'm sure I've jumped into a couple of
pitfalls.
The java method signature is getUnsafe*(long base, long offset)
I added the following method to compiler.cpp:


virtual Operand *unsafeLoad(unsigned size, unsigned int selectSize,
Operand *base, Operand *offset, bool isFloat) {
assert(&c, static_cast<Value*>(base)->type == ValueGeneral);
assert(&c, static_cast<Value*>(offset)->type == ValueGeneral);

Value *sourceAddress = static_cast<Value*>(this->add(size, base,
offset));

Value* result = value(&c, isFloat ? ValueFloat : ValueGeneral);
appendMemory(&c, sourceAddress, 0, NULL, selectSize /
TargetBytesPerWord, result);

return result;
}

And extended the compile.cpp / instrinsic(MyThread,Frame,object)
method to emit the intrinsic asm:


if (MATCH(methodName(t, target), "getUnsafeInt"))
{
if (MATCH(methodSpec(t, target), "(JJ)I"))
{
Compiler::Operand *offset = frame->popLong();
Compiler::Operand *base = frame->popLong();

frame->pushInt(c->unsafeLoad(8, 4, base, offset, false));
return true;
}
}

Just did a quick test, seems to work fine for int and floats, however,
since booleans use 1-byte on some systems I was wondering how to mask
the result value.

Thanks

Joel Dice

unread,
Mar 6, 2012, 5:54:08 PM3/6/12
to Avian
On Tue, 6 Mar 2012, Daniel Jaeger wrote:

>> That sounds reasonable. I'm afraid I don't have any more guesses about
>> the float argument passing problem. I can't imagine what would cause it
>> to work most of the time but occasionally stop working. I presume you've
>> fixed up all the incorrect uses of JNIEnv*, right?
> Yes, the only way the engine can access the JNIEnv* is either by pass-
> through
> or by invoking MakeEnvironment() which grabs the current local thread.
> I already mentioned it, maybe it's related to the Jikes compiler which
> is
> built into the engine to dynamically recompile classes. I'm going to
> disable
> it and switch to javac to check if I can produce the error again.

Yeah, I'll admit I've never tested dynamic class redefinition, so there
may be bugs with that.

>> I've lost track of what you're trying to accomplish here. I thought this
>> came up because you wanted to be able to access Java fields from native
>> code without worrying about a GC being initiated from another thread and
>> moving the object out from under you. If that's all you're worried about,
>> there's no need to pause all the other threads, just enter
>> Thread::ActiveState if you haven't already. Is the some other reason you
>> need all the other threads paused?
> The engine is not thread safe for performance reasons so it would be
> great to be in control over what happens for various reasons.

The normal way to protect a non-thread-safe resource would be to protect
it with a mutex (or monitor in Java terms). Acquiring a mutex will be a
lot less expensive performance-wise than any attempt to pause all other
threads.

>
>> My point is that there's already an official, well-defined way to do this
>> in Java: phantom references. No need to hack the VM, and your application
>> code remains portable across VMs.
> That is true, but our code will not be easily portable anyway as there
> will be heavy
> use of the Avian_ calling convention.

I was assuming you'd implement two versions of each performance-sensitive
native method: a VM-independent JNI one and an Avian-specific one. Even
if you are committed to using Avian exclusively, though, I think you'll be
better off using an unmodified version rather than maintain a fork with
your customizations. The more your version diverges, the less help I'll
be able to provide, and the more difficult it will be for you to merge
upstream fixes and features.

> While I know that we can close our objects early on by calling a close
> method similar
> to SWT I want to ensure that it's impossible to break the engine on
> the native
> side through java. That basically means disabling multi threading.

Or mutual exclusion. If you really want to disable multithreading,
though, you can patch startThread to e.g. throw an
UnsupportedOperationException.

That looks reasonable for 32-bit values. FWIW, I went ahead and
implemented the relevant sun.misc.Unsafe methods, which cover the 8, 16,
32, and 64-bit cases:

http://oss.readytalk.com/gitweb?p=avian.git;a=commitdiff;h=e8e3c9066feca6e50d28870292522435d5869c99

When using such methods to access fields in native structures, you'll need
to take into account the details of the ABI you're building for, including
the sizes of types like bool and padding dictated by alignment
constraints. Thus, on one platform you might use

boolean value = unsafe.getByte(myStruct + 43) != 0;

to access a given bool field, whereas on another platform you might use

boolean value = unsafe.getInt(myStruct + 48) != 0;

for the same field because the size of the type is different and has
different alignment requirements, and the fields before it in the struct
might have different sizes and alignments. I definitely recommend against
using e.g. getInt to access a single byte, since it exposes you to
endianness issues and may trigger segfaults (due to reading past the end
of a structure) and bus errors (due to misaligned access).

SWT uses native calls to access the standard C sizeof and offsetof macros to
determine such information at runtime. You could also calculate them at
build time and generate a Java file with named constants to reference in
your code.

Daniel Jaeger

unread,
Mar 6, 2012, 7:38:12 PM3/6/12
to Avian
>> …
>> built into the engine to dynamically recompile classes. I'm going to
>> disable it and switch to javac to check if I can produce the error again.
> Yeah, I'll admit I've never tested dynamic class redefinition, so there may be bugs with that.
Ah, there's a misunderstanding. We're actually stopping the VM,
recompiling, restarting the VM while the engine is running.
But doing this while the VM is alive would be absolutely great!


> The normal way to protect a non-thread-safe resource would be to protect it with a mutex (or monitor in Java terms). Acquiring a
> mutex will be a lot less expensive performance-wise than any attempt to pause all other threads.
That's true, but I want to avoid blocking the main render thread. But
I will reconsider all options.


> I was assuming you'd implement two versions of each performance-sensitive native method: a VM-independent JNI one and an Avian-
> specific one. Even if you are committed to using Avian exclusively, though, I think you'll be better off using an unmodified
> version rather than maintain a fork with your customizations. The more your version diverges, the less help I'll be able to
> provide, and the more difficult it will be for you to merge upstream fixes and features.
Yes, however it will be difficult as we'll have to implement more
unsafe methods for field access etc.
But if the project activity does not grow much beyond what it is right
now, it should be possible to pull your changes into the code base.
And I do not intend to modify existing code, merely build on what
exists.
So any merge tool should be able to handle it.


> Or mutual exclusion. If you really want to disable multithreading, though, you can patch startThread to e.g. throw an
> UnsupportedOperationException.
Thanks, I'll take a note of it.

>That looks reasonable for 32-bit values. FWIW, I went ahead and implemented the relevant sun.misc.Unsafe methods,
> which cover the 8, 16, 32, and 64-bit cases:
That is great. I'll try it out as soon as possible. However I dislike
the fact that the sun.misc.Unsafe methods are not static and require
the allocation of a proxy object. Isn't there a cost involved in
passing the "this" pointer around on the stack for each get*
invocation?
Or will this be omitted by the compiler because the object has been
directly popped off?

> When using such methods to access fields in native structures, you'll need to take into account the details of the ABI you're
> building for, including the sizes of types like bool and padding dictated by alignment constraints. Thus, on one platform you
> might use
Yes, my idea is to pass the POD information & struct layout during
runtime VM initialization to the java classes.

> I definitely recommend against using e.g. getInt to access a single byte, since it exposes you to endianness issues and
> may trigger segfaults (due to reading past the end of a structure) and bus errors (due to misaligned access).
You are absolutly right, I don't know why I didn't even consider
loading less than 32bit -


Thanks

Joel Dice

unread,
Mar 6, 2012, 8:58:40 PM3/6/12
to Avian
On Tue, 6 Mar 2012, Daniel Jaeger wrote:

> Ah, there's a misunderstanding. We're actually stopping the VM,
> recompiling, restarting the VM while the engine is running.
> But doing this while the VM is alive would be absolutely great!

It should work, especially if you create a new classloader for each new
set of classes.

>> I was assuming you'd implement two versions of each performance-sensitive native method: a VM-independent JNI one and an Avian-
>> specific one. Even if you are committed to using Avian exclusively, though, I think you'll be better off using an unmodified
>> version rather than maintain a fork with your customizations. The more your version diverges, the less help I'll be able to
>> provide, and the more difficult it will be for you to merge upstream fixes and features.
> Yes, however it will be difficult as we'll have to implement more
> unsafe methods for field access etc.

If you're using the Avian calling convention, you'll have direct access to
object references and fields. You shouldn't need to add any new methods.
If you can't find what you need, let me know.

>> That looks reasonable for 32-bit values. FWIW, I went ahead and implemented the relevant sun.misc.Unsafe methods,
>> which cover the 8, 16, 32, and 64-bit cases:
> That is great. I'll try it out as soon as possible. However I dislike
> the fact that the sun.misc.Unsafe methods are not static and require
> the allocation of a proxy object. Isn't there a cost involved in
> passing the "this" pointer around on the stack for each get*
> invocation?

No, the compiler will ignore a value that's pushed onto the stack but
never used, and since the get/put methods are intrinsics, there's no
method invocation at all; just a straight register-to-memory or
memory-to-register move.

> Or will this be omitted by the compiler because the object has been
> directly popped off?

Yes. The value isn't needed, so it's never actually loaded, pushed, or
popped in the generated code.

Pablo Guerrero

unread,
Mar 11, 2012, 6:51:10 AM3/11/12
to av...@googlegroups.com
Hi all,

I've just started to play with Avian, and I'm interested in the iOS port.

Do you plan to support openjdk for iOS or just the avian classpath? Even if you don't plan to do it, do you think that it woud be feasible with a reasonable amount of effort?

Thanks,
Pablo

On Friday, July 29, 2011 11:36:36 PM UTC+2, mzechner wrote:
Hi there,

i contacted Joel in private a few days ago and of course he prefers to
have a public conversation about the given topic. Sorry Joel, i should
have known better. I give the exact same answer to people that bug me
via e-mail about the framework :/

I'll cut and paste our convo in in-line style and end with a few
follow up comments.

[ME]
my name is Mario Zechner, i'm the creator of libgdx [1], a Java/C/C++
based cross-platform game development framework for the desktop and
Android. I've already infiltrated the Avian VM Google group. I hope
you don't mind me writting you directly. I'll try to keep it short
(but will most likely fail, sorry :/).

[JOEL]
I don't mind you writing me directly, but I do prefer to answer
questions about Avian on the Google group so others can benefit.  That
way, I don't have to repeat myself if others have the same questions,
and the archives are available to anyone searching the Web for Avian-
related information.

I'm sure you're not the only one interested in using Avian on iOS.
[CUT SOMETHING HERE AS I DON'T KNOW IF ITS CONFIDENTIAL] So, I would
prefer that any further discussion happen on the group.  Plus, I know
that there are some Apple experts subscribed to it who may be able to
help you out better than I can.

[ME]
First of all i have to say that i was highly impressed with what you
did with Avian. It's a very impressive and well executed project,
exactly the kind of lightweight yet capable JVM i've been looking for.

What i'm interested in the most is the AOT capabilities of Avian.
Cutting a long story short: i want to AOT compile Java bytecode to
Mach-o to be able to (statically) link it against iOS binaries. This
means i need the following:

- AOT compilation of byte code to a Mach-o container for ARMv6
(preferring the later of course), using the iOS ABI [2] (which seems
to be standard). binaryToObject seems to support that for a couple of
platforms, but not for ARM.

[JOEL]
Adding ARM support to mach-o.cpp should not be difficult.  If you look
at that file, you'll see that most of the code is architecture-
agnostic.

The more challenging part might be porting arm.S, arm.h, and arm.cpp,
which currently support Linux only.  I haven't looked at how the iOS
ABI compares to the ABI we use for Linux.  I know the Linux and OS X
PowerPC ABIs are different enough that it was non-trivial to add
support for the former after we already had the latter working.

[ME]
- a runtime environment that allows me to execute the AOT compiled
bytecode (gc, locks & threading, a minimal runtime lib etc., basically
anything a JVM would provide for JIT compiled/interpreted code
execution). Avian seems to provide just that, in a nice cross-platform
way. Since there's a posix and darwin backend i'd assume only minimal
modifications would be necessary.

[JOEL]
I don't know much about iOS, but a quick look at the Wikipedia page
indicates that it shares its Darwin foundation with OS X and only
really differs at the UI level.  In that case, there should be little
or no porting needed beyond the architecture- and ABI-specific parts I
mentioned above.

[ME]
- access to JNI methods, statically linked to the AOT compiled Mach-o
object file. That seems to be possible as well with some JNIEXPORT
magic if i understood things correctly.

[JOEL]
Yes, that should be easy if iOS at least allows dlsym calls to the
current executable.  Otherwise, we'll have to get creative (e.g. build
a table of function pointers by hand or via code generation).

[ME]
So, apart from the missing supported vor ARM + Mach-o, everything
seems to be covered. Here are a few things i'm worried about:

- iOS is very restrictive in terms of dynamic loading, read: it
doesn't allow shared libs nor does it allow setting memory to be
executeable. With a bootimage build, would i need the later? It seems
to me that the AOT compiled code's symbols are directly exported
through the container file formats like ELF or Mach-o, i might be
wrong though. My knowledge of these things is very rusty so i couldn't
make 100% sense of the code i checked.

[JOEL]
If everything's in the bootimage, and iOS respects the readable/
writable/executable flags on the Mach-O segment in which the bootimage
resides, it should work fine.  As I said above, though, if it doesn't
even allow dlsym calls to the current executable, we'll have to do
something special to make JNI work.

[ME]
-  As oposed to other projects like Mono et. al you seem to have your
own GC instead of  the Boehm-Demers-Weiser GC that's commonly used. Is
there any platform specific trickery involved that would prevent the
GC from working on iOS?

[JOEL]
There's no trickery there, so I would not expect any problems.  The
Boehm GC must use all kinds of platform-specific trickery because it
needs to search for root references anywhere they might exist with
little or no help from the application or VM.  That means it must find
the extent of each thread's stack, know how to force the contents all
relevant registers to memory, etc.  Avian's GC expects the VM to tell
it where all the root references are, and the VM is designed such that
those references are all tracked explicitly; there's no need to search
for them.

[ME]
- What ABI do you target with the ARM backend?

[JOEL]
I'm not really sure what it's even called :)  It's whatever modern
Linux-on-ARM systems use.  Some call it the GNU EABI, but I take it
that's not the official name.  Here are some links to the relevant
specifications:

http://wiki.debian.org/ArmEabiPort#References

Someone else did most of the work on the ARM port, so I'm not really
an expert.  For the parts I contributed, I relied heavily on the
assembly output from GCC to answer questions about the ABI rather than
look at specifications.

[ME]
- Is there support for VFP or do you use softfp?

[JOEL]
The ARM port of the JIT compiler (which is also used for AOT
compilation) defers to out-of-line C++ helper functions for all
floating point ops. Whether those helper functions use hardware or
software FP depends on the compiler/flags used to build Avian.

[ME]
I'd appreciate any pointers you could throw my way. I've looked at
various alternatives, and the thing that draws me the most to Avian is
it's minimal runtime library as well as it's comparably small
code-base. I'm aware that it might not be production ready yet, but i
think i could cope with that if i was able to get an iOS port going.

[JOEL]
FWIW, we've been deploying Avian-based products at my company for over
a year now, so it's production ready as far as our needs go.

[ME]
I'd love to collaborate, even though you might not want C++ code
contributions from me. My brain's been poisoned with Java over the
last couple of years, and my C++ knowledge (and the little ARM
assembler knowledge i had) have mostly been replaced with hideous EE
framework nightmares. I'd still offer my help were possible, be it in
form of code or testing.

[JOEL]
The lack of comments/documentation in e.g. arm.cpp and the rest of the
VM would probably be the biggest hurdle even if you were a C++ expert.
That's something I hope to improve eventually, but writing
documentation isn't much fun :)  Anyway, if you want to get started on
this, I'll be happy to help as time allows.  I might start tinkering
with it myself if I can get an iOS dev/test environment set up.

[ME]
Another thing i could provide is a community full of people that would
love to help testing Avian, on iOS but potentially also on other
platforms. If Avian turns out to be stable enough it could be a nice
alternative for people that want their games to run "natively",
without the need for an installed JRE. Most of the games produced by
the community do not demand that much CPU-side performance, so even if
Avian can't reach HotSpot in terms of execution speed, it would still
be more than good enough for 99% of those games.

Finally I'd like to ask what your plans are in terms of potential
commercialization of Avian. I provide virtually all my non-day-job
code under a very permissive OSS licence (Apache 2) so other's can
benefit from it. I generate income by support contracts, workshops and
the like. Getting those gigs is merely a question of reputation. A
successful OSS project can get you that.

[JOEL]
I have no commercial plans.  I'm just in it for the technical
challenge and learning opportunities.  My day job pays the bills, and
I even get to work on Avian on company time since it's become an
essential piece of infrastructure for us.

[ME]
An iOS port of Avian that is compatible with the Appstore TOS could be
a huge seller of course, so i'd understand if you'd go a non OSS or
potentially (A)GPL road in that case. If you decided to keep the
current licence you'd make a ton of people happy though :)

[JOEL]
We'll definitely stick with the current license.

[ME]
Thanks if you read that far, this thing got longer than intended.
Whatever the outcome with regards to the above things, keep up the
insanely good work. I'll definitely give Avian a try for a couple of
projects of mine on the desktop.

[JOEL]
Thanks for the feedback.  libgdx looks like a cool project, and I
agree that Avian would be a good choice to make it run on iOS.  I'm
definitely curious to find out how much work the port would require.

FOLLOW-UP
Thanks to Joel for taking the time and answer my lengthy inquiry. Here
are the things that would need to be resolved:

- check ARM version used. ARMv6 would be preferred. Didn't have time
to look into that yet.
- check VFP support. Currently Avian calls out to C methods for all
floating point related functionality. That might actually be a good
thing in case of iOS. Though it incurs a function call as i understood
it, there's an oportunity to go from softfp to VFP and eventually also
have a NEON backend.
- check current ARM ABI against iOS ABI. I had a quick look and there
seem to be a few differences, nothing to worrying though.
- check dlsym availability. I couldn't find hard facts on this matter,
but from the looks of it it is at least possible to load symbols from
statically linked object files on iOS.
- extent mach-o support to ARM.

One thing i thought about: an alternative to outputting object files/
boot images would be to actually output an intermediate
representation, either C or LLVM IL. What i've seen so far that might
actually be possible. This way one could use the LLVM toolchain XCode
uses for iOS to compile the final binary which might make some things
easier. Input on that would be appreciated.

I'm currently moving to SF and will have a bit of time over the next 3
months. I'm not sure i'm up for the task (and i also lack a mac dev
environment) but i think it would be an awesome side-project.

Sorry for the mail-bomb, and thanks Joel for providing us with such an
awesome project!

On Friday, July 29, 2011 11:36:36 PM UTC+2, mzechner wrote:
Hi there,

i contacted Joel in private a few days ago and of course he prefers to
have a public conversation about the given topic. Sorry Joel, i should
have known better. I give the exact same answer to people that bug me
via e-mail about the framework :/

I'll cut and paste our convo in in-line style and end with a few
follow up comments.

[ME]
my name is Mario Zechner, i'm the creator of libgdx [1], a Java/C/C++
based cross-platform game development framework for the desktop and
Android. I've already infiltrated the Avian VM Google group. I hope
you don't mind me writting you directly. I'll try to keep it short
(but will most likely fail, sorry :/).

[JOEL]
I don't mind you writing me directly, but I do prefer to answer
questions about Avian on the Google group so others can benefit.  That
way, I don't have to repeat myself if others have the same questions,
and the archives are available to anyone searching the Web for Avian-
related information.

I'm sure you're not the only one interested in using Avian on iOS.
[CUT SOMETHING HERE AS I DON'T KNOW IF ITS CONFIDENTIAL] So, I would
prefer that any further discussion happen on the group.  Plus, I know
that there are some Apple experts subscribed to it who may be able to
help you out better than I can.

[ME]
First of all i have to say that i was highly impressed with what you
did with Avian. It's a very impressive and well executed project,
exactly the kind of lightweight yet capable JVM i've been looking for.

What i'm interested in the most is the AOT capabilities of Avian.
Cutting a long story short: i want to AOT compile Java bytecode to
Mach-o to be able to (statically) link it against iOS binaries. This
means i need the following:

- AOT compilation of byte code to a Mach-o container for ARMv6
(preferring the later of course), using the iOS ABI [2] (which seems
to be standard). binaryToObject seems to support that for a couple of
platforms, but not for ARM.

[JOEL]
Adding ARM support to mach-o.cpp should not be difficult.  If you look
at that file, you'll see that most of the code is architecture-
agnostic.

The more challenging part might be porting arm.S, arm.h, and arm.cpp,
which currently support Linux only.  I haven't looked at how the iOS
ABI compares to the ABI we use for Linux.  I know the Linux and OS X
PowerPC ABIs are different enough that it was non-trivial to add
support for the former after we already had the latter working.

[ME]
- a runtime environment that allows me to execute the AOT compiled
bytecode (gc, locks & threading, a minimal runtime lib etc., basically
anything a JVM would provide for JIT compiled/interpreted code
execution). Avian seems to provide just that, in a nice cross-platform
way. Since there's a posix and darwin backend i'd assume only minimal
modifications would be necessary.

[JOEL]
I don't know much about iOS, but a quick look at the Wikipedia page
indicates that it shares its Darwin foundation with OS X and only
really differs at the UI level.  In that case, there should be little
or no porting needed beyond the architecture- and ABI-specific parts I
mentioned above.

[ME]
- access to JNI methods, statically linked to the AOT compiled Mach-o
object file. That seems to be possible as well with some JNIEXPORT
magic if i understood things correctly.

[JOEL]
Yes, that should be easy if iOS at least allows dlsym calls to the
current executable.  Otherwise, we'll have to get creative (e.g. build
a table of function pointers by hand or via code generation).

[ME]
So, apart from the missing supported vor ARM + Mach-o, everything
seems to be covered. Here are a few things i'm worried about:

- iOS is very restrictive in terms of dynamic loading, read: it
doesn't allow shared libs nor does it allow setting memory to be
executeable. With a bootimage build, would i need the later? It seems
to me that the AOT compiled code's symbols are directly exported
through the container file formats like ELF or Mach-o, i might be
wrong though. My knowledge of these things is very rusty so i couldn't
make 100% sense of the code i checked.

[JOEL]
If everything's in the bootimage, and iOS respects the readable/
writable/executable flags on the Mach-O segment in which the bootimage
resides, it should work fine.  As I said above, though, if it doesn't
even allow dlsym calls to the current executable, we'll have to do
something special to make JNI work.

[ME]
-  As oposed to other projects like Mono et. al you seem to have your
own GC instead of  the Boehm-Demers-Weiser GC that's commonly used. Is
there any platform specific trickery involved that would prevent the
GC from working on iOS?

[JOEL]
There's no trickery there, so I would not expect any problems.  The
Boehm GC must use all kinds of platform-specific trickery because it
needs to search for root references anywhere they might exist with
little or no help from the application or VM.  That means it must find
the extent of each thread's stack, know how to force the contents all
relevant registers to memory, etc.  Avian's GC expects the VM to tell
it where all the root references are, and the VM is designed such that
those references are all tracked explicitly; there's no need to search
for them.

[ME]
- What ABI do you target with the ARM backend?

[JOEL]
I'm not really sure what it's even called :)  It's whatever modern
Linux-on-ARM systems use.  Some call it the GNU EABI, but I take it
that's not the official name.  Here are some links to the relevant
specifications:

http://wiki.debian.org/ArmEabiPort#References

Someone else did most of the work on the ARM port, so I'm not really
an expert.  For the parts I contributed, I relied heavily on the
assembly output from GCC to answer questions about the ABI rather than
look at specifications.

[ME]
- Is there support for VFP or do you use softfp?

[JOEL]
The ARM port of the JIT compiler (which is also used for AOT
compilation) defers to out-of-line C++ helper functions for all
floating point ops. Whether those helper functions use hardware or
software FP depends on the compiler/flags used to build Avian.

[ME]
I'd appreciate any pointers you could throw my way. I've looked at
various alternatives, and the thing that draws me the most to Avian is
it's minimal runtime library as well as it's comparably small
code-base. I'm aware that it might not be production ready yet, but i
think i could cope with that if i was able to get an iOS port going.

[JOEL]
FWIW, we've been deploying Avian-based products at my company for over
a year now, so it's production ready as far as our needs go.

[ME]
I'd love to collaborate, even though you might not want C++ code
contributions from me. My brain's been poisoned with Java over the
last couple of years, and my C++ knowledge (and the little ARM
assembler knowledge i had) have mostly been replaced with hideous EE
framework nightmares. I'd still offer my help were possible, be it in
form of code or testing.

[JOEL]
The lack of comments/documentation in e.g. arm.cpp and the rest of the
VM would probably be the biggest hurdle even if you were a C++ expert.
That's something I hope to improve eventually, but writing
documentation isn't much fun :)  Anyway, if you want to get started on
this, I'll be happy to help as time allows.  I might start tinkering
with it myself if I can get an iOS dev/test environment set up.

[ME]
Another thing i could provide is a community full of people that would
love to help testing Avian, on iOS but potentially also on other
platforms. If Avian turns out to be stable enough it could be a nice
alternative for people that want their games to run "natively",
without the need for an installed JRE. Most of the games produced by
the community do not demand that much CPU-side performance, so even if
Avian can't reach HotSpot in terms of execution speed, it would still
be more than good enough for 99% of those games.

Finally I'd like to ask what your plans are in terms of potential
commercialization of Avian. I provide virtually all my non-day-job
code under a very permissive OSS licence (Apache 2) so other's can
benefit from it. I generate income by support contracts, workshops and
the like. Getting those gigs is merely a question of reputation. A
successful OSS project can get you that.

[JOEL]
I have no commercial plans.  I'm just in it for the technical
challenge and learning opportunities.  My day job pays the bills, and
I even get to work on Avian on company time since it's become an
essential piece of infrastructure for us.

[ME]
An iOS port of Avian that is compatible with the Appstore TOS could be
a huge seller of course, so i'd understand if you'd go a non OSS or
potentially (A)GPL road in that case. If you decided to keep the
current licence you'd make a ton of people happy though :)

[JOEL]
We'll definitely stick with the current license.

[ME]
Thanks if you read that far, this thing got longer than intended.
Whatever the outcome with regards to the above things, keep up the
insanely good work. I'll definitely give Avian a try for a couple of
projects of mine on the desktop.

[JOEL]
Thanks for the feedback.  libgdx looks like a cool project, and I
agree that Avian would be a good choice to make it run on iOS.  I'm
definitely curious to find out how much work the port would require.

FOLLOW-UP
Thanks to Joel for taking the time and answer my lengthy inquiry. Here
are the things that would need to be resolved:

- check ARM version used. ARMv6 would be preferred. Didn't have time
to look into that yet.
- check VFP support. Currently Avian calls out to C methods for all
floating point related functionality. That might actually be a good
thing in case of iOS. Though it incurs a function call as i understood
it, there's an oportunity to go from softfp to VFP and eventually also
have a NEON backend.
- check current ARM ABI against iOS ABI. I had a quick look and there
seem to be a few differences, nothing to worrying though.
- check dlsym availability. I couldn't find hard facts on this matter,
but from the looks of it it is at least possible to load symbols from
statically linked object files on iOS.
- extent mach-o support to ARM.

One thing i thought about: an alternative to outputting object files/
boot images would be to actually output an intermediate
representation, either C or LLVM IL. What i've seen so far that might
actually be possible. This way one could use the LLVM toolchain XCode
uses for iOS to compile the final binary which might make some things
easier. Input on that would be appreciated.

I'm currently moving to SF and will have a bit of time over the next 3
months. I'm not sure i'm up for the task (and i also lack a mac dev
environment) but i think it would be an awesome side-project.

Sorry for the mail-bomb, and thanks Joel for providing us with such an
awesome project!

Joel Dice

unread,
Mar 11, 2012, 2:25:16 PM3/11/12
to av...@googlegroups.com
On Sun, 11 Mar 2012, Pablo Guerrero wrote:

> Hi all,
> I've just started to play with Avian, and I'm interested in the iOS port.
>
> Do you plan to support openjdk for iOS or just the avian classpath? Even if
> you don't plan to do it, do you think that it woud be feasible with
> a�reasonable�amount�of effort?

I haven't tried it yet, but it shouldn't be too hard. A good place to
start would be to modify the makefile
git://oss.readytalk.com/hello-ios.git to accept an openjdk= option. It
might help to look at app.mk in
git://oss.readytalk.com/avian-swt-examples.git for an example of how to do
that. It probably won't work without a few code changes, but they should
be pretty easy.

I'll give it a try when I have some time and access to an iOS device.

Pablo Guerrero

unread,
Mar 11, 2012, 4:18:30 PM3/11/12
to av...@googlegroups.com
That's really good news. It's not really urgent for me to work with iOS right now, but knowing that there is no obvious problem to support it, it's really good.

I'll have a look at the current iOS example. 

Cheers,
Pablo


On Sunday, March 11, 2012 7:25:16 PM UTC+1, Joel Dice wrote:
On Sun, 11 Mar 2012, Pablo Guerrero wrote:

> Hi all,
> I've just started to play with Avian, and I'm interested in the iOS port.
>
> Do you plan to support openjdk for iOS or just the avian classpath? Even if
> you don't plan to do it, do you think that it woud be feasible with

> a�reasonable�amount�of effort?

Reply all
Reply to author
Forward
0 new messages