10

I am using a Motorola FX9500 RFID reader, which runs Linux with the jamvm version 1.5.0 on it (I can only deploy applications to it - I cannot change the Java VM or anything so my options are limited) - here's what I see when I check the version:

[cliuser@FX9500D96335 ~]$ /usr/bin/jamvm -version
java version "1.5.0"
JamVM version 1.5.4
Copyright (C) 2003-2010 Robert Lougher <rob@jamvm.org.uk>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2,
or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

Build information:

Execution Engine: inline-threaded interpreter with stack-caching
Compiled with: gcc 4.2.2

Boot Library Path: /usr/lib/classpath
Boot Class Path: /usr/local/jamvm/share/jamvm/classes.zip:/usr/share/classpath/glibj.zip

I need to write an application so I grabbed the Oracle Java SDK 1.5.0 and installed it onto my Windows 7 PC, so it has this version:

C:\>javac -version
javac 1.5.0

Am I being too idealistic in considering that an application I compile with that compiler would work correctly on the aforementioned JamVM? Anyway, pressing on in ignorance I write this little application:

public final class TestApp {
    public static void main(final String[] args) {
        long p = Long.MIN_VALUE;
        int o = (int)(-(p + 10) % 10);
        System.out.println(o);
    }
}

Compile it with the aforementioned javac compiler and run it on the PC like so:

C:\>javac TestApp.java

C:\>java TestApp
8

All fine there. Life is good, so I take that .class file and place it on the FX9500 and run it like so:

[cliuser@FX9500D96335 ~]$ /usr/bin/jamvm TestApp
-2

Eek, what the...as you can see - it returns a different result.

So, why and who's wrong or is this something like the specification is not clear about how to deal with this calculation (surely not)? Could it be that I need to compile it with a different compiler?


Why Do I Care About This?

The reason I came to this situation is that a calculation exactly like that happens inside java.lang.Long.toString and I have a bug in my real application where I am logging out a long and getting a java.lang.ArrayIndexOutOfBoundsException. Because the value I am wanting to log may very well be at the ends of a Long.

I think I can work around it by checking for Long.MIN_VALUE and Long.MAX_VALUE and logging "Err, I can't tell you the number but it is really Long.XXX, believe me, would I lie to you?". But when I find this, I feel like my application is built on a sandy foundation now and it needs to be really robust. I am seriously considering just saying that JamVM is not up to the job and writing the application in Python (since the reader also has a Python runtime).

I'm kind of hoping that someone tells me I'm a dullard and I should have compiled it on my Windows PC like .... and then it would work, so please tell me that (if it is true, of course)!


Update

Noofiz got me thinking (thanks) and I knocked up this additional test application:

public final class TestApp2 {
    public static void main(final String[] args) {

        long p = Long.MIN_VALUE + 10;

        if (p != -9223372036854775798L) {
            System.out.println("O....M.....G");
            return;
        }

        p = -p;

        if (p != 9223372036854775798L) {
            System.out.println("W....T.....F");
            return;            
        }

        int o = (int)(p % 10);

        if (o != 8) {
            System.out.println("EEEEEK");
            return;
        }

        System.out.println("Phew, that was a close one");
    }
}

I, again, compile on the Windows machine and run it.

It prints Phew, that was a close one

I copy the .class file to the contraption in question and run it.

It prints...

...wait for it...

W....T.....F

Oh dear. I feel a bit woozy, I think I need a cup of tea...

Update 2

One other thing I tried, that did not make any difference, was to copy the classes.zip and glibj.zip files off of the FX9500 to the PC and then do a cross compile like so (that must mean the compiled file should be fine right?):

javac -source 1.4 -target 1.4 -bootclasspath classes.zip;glibj.zip -extdirs "" TestApp2.java

But the resulting .class file, when run on the reader prints the same message.

kmp
  • 10,535
  • 11
  • 75
  • 125
  • 1
    +1 for a yarn well spun (i.e. a question well and entertainingly written), I think. Isn't there a jamvm compiler for Windows you could try? Not sure a regular SDK compiler does it... – Simon Hellinger Apr 05 '13 at 08:13
  • Thanks. I couldn't find a jamvm compiler - I think it is just a virtual machine. – kmp Apr 05 '13 at 09:00
  • 1
    Did you try to use custom modulus operation. I mean a - (a/b)*b instead of a%b. May be there is some difference in implementation. Add and neg operations are to straight forward to cause problems. – Mikhail Apr 05 '13 at 09:00

3 Answers3

5

I wrote JamVM. As you would probably guess, such errors would have been noticed by now, and JamVM wouldn't pass even the simplest of test suites with them (GNU Classpath has its own called Mauve, and OpenJDK has jtreg). I regularly run on ARM (the FX9500 uses a PXA270 ARM) and x86-64, but various platforms get tested as part of IcedTea.

So I haven't much of a clue as to what's happened here. I would guess it only affects Java longs as these are used infrequently and so most programs work. JamVM maps Java longs to C long longs, so my guess would be that the compiler used to build JamVM is producing incorrect code for long long handling on the 32-bit ARM.

Unfortunately there's not much you can do (apart from avoid longs) if you can't replace the JVM. The only thing you can do is try and turn the JIT off (a simple code-copying JIT, aka inline-threading). To do this use -Xnoinlining on the command line, e.g.:

jamvm -Xnoinlining ...

  • 1
    One of your (kmp) comments implies that JamVM is part of the software supplied with the FX9500 by Motorola. Is this true? If it is, there's no guarantees that JamVM hasn't been modified and/or hacked. You could try asking Motorola for the source, which must be available as I release JamVM under the GPL... – user2251099 Apr 06 '13 at 02:06
  • Thank you very much - I am so surprised about this - I thought a bug in something so simple in jamvm would be highly unlikely - the idea about how it was built seems likely - I wish I could replace it myself but it is completely locked down so ive asked motorola for help – kmp Apr 06 '13 at 07:36
  • Just to update you - passing -Xnoinlining does not make any difference. – kmp Apr 08 '13 at 07:26
4

The problem is in different modulus implementations:

public static long mod(long a, long b){
    long result = a % b;
    if (result < 0)
    {
        result += b;
    }
    return result;
}

this code returns -2, while this:

public static long mod2(long a, long b){
    long result = a % b;
    if (result > 0 && a < 0)
    {
        result -= b;
    }
    return result;
}

returns 8. Reasons why JamVM is doing this way are behind my understanding.

From JLS:

15.17.3. Remainder Operator %

The remainder operation for operands that are integers after binary numeric promotion (§5.6.2) produces a result value such that (a/b)*b+(a%b) is equal to a.

According to this JamVM breaks language specification. Very bad.

Mikhail
  • 4,175
  • 15
  • 31
  • Well, jamvm source code is available, can always take a look. However I don't remember I had any problems of a kind with jamvm, but I compiled it myself and only for x86 target (don't know what that Motorola's contraption is running). – Archie Apr 05 '13 at 09:18
  • I don't think it is the modulus operator - see my update to the question – kmp Apr 05 '13 at 10:31
  • 1
    This is a real WTF! If JVM has a such wired interpretation of specifications about primitive types, I don't understand how are you going to use it for even medium size project. – Mikhail Apr 05 '13 at 10:42
  • According to the JamVM web page, it conforms to the JVM specification version 2 (blue book). Maybe contact the JamVM developer to discuss this issue? – mthmulders Apr 05 '13 at 10:46
  • Yup, I am now really, really, worried about this project - who knows what kind of insanity I am going to run into. I really think that the only reasonable thing I can do is drop Java entirely and hope that the Python runtime it has on it doesn't also have fundamental flaws like this in it. I guess it's highly unlikely that Motorola would release an update to it with a better JVM (as a user of the device, I don't have the permissions to install a different JVM myself so even if it's already fixed in a newer JamVM - it would not help me) . – kmp Apr 05 '13 at 10:47
2

I would have commented, but for some reason, that requires reputation.

Long negation doesn't work on this device. I don't understand its exact nature, but if you do two unary minuses you do get back to where you started, e.g. x=10; -x==4294967286; -x==10. 4294967286 is very close to Integer.MAX_VALUE*2 (2147483647*2 = 4294967294). It's even closer to Integer.MAX_VALUE*2-10!

It seems to be isolated to this one operation, and doesn't affect longs in a further fundamental way. It's simple to avoid the operation in your own code, and with some dextrous abuse of the bootclasspath can avoid the calls in GNU Classpath code, replacing them with *-1s. If you need to start your application from the device GUI, you can include the -Xbootclasspath=... switch into the args parameter for it to be passed to JamVM).

The bug is actually already fixed in latter (than the latest release) JamVM code: * https://github.com/ansoncat/jamvm/commit/736c2cb76baf1fedddc1eda5825908f5a0511373 * https://github.com/ansoncat/jamvm/commit/ac83bdc886ac4f6e60d684de1b4d0a5e90f1c489

though doesn't help us with the fixed version on the device. Rob Lougher has mentioned this issue as a reason for releasing a new version of JamVM, though I don't know when this would be, or whether Motorola would be enough convinced to update their firmware.

The FX9500 is actually a repackaged Sirit IN610, meaning that both devices share this bug. Sirit are way friendlier that Motorola and are providing a firmware upgrade, to be available in the near future. I hope that Motorola will also include the fix, though I don't know the details of the arrangement between the two parties.

Either way, we have a very big application running on the FX9500, and the long negation operation hasn't proved to be an impassable barrier.

Good luck, Dan.

Dan
  • 487
  • 1
  • 4
  • 10
  • Thanks Dan - especially for the tip about -Xbootclasspath. Actually, the only place where we had a problem was where we were doing a String.format("%d",x) and x was any negative number (-1 would do it) - then it would crash. I just wrote my own String.format method that did not internally convert integers into longs and used that instead (we only ever used it when writing log messages - luckily the application was pretty simple). If I ever do use the same device again for anything non trivial I think I would use the python interpreter instead. – kmp Sep 04 '13 at 17:32