Thanks to the work of Gary Benson on implementing and merging the Zero-Assembler Project, and Greg Lewis' work bringing it to OpenJDK BSD Port, it's now possible to bootstrap OpenJDK 7 on Mac OS X 10.5/PPC.
Gary Benson's Zero Assembler provides a portable implementation of the JVM intepreter that -- unlike the existing JVM implementations -- relies on very little assembler to provide an acceptably performing but highly portable VM, opening the door to supporting Mac OS X PPC with very little additional work.
I've committed the few small fixes to get OpenJDK running on Mac OS X 10.5/PPC, and have bootstrapped an initial OpenJDK 7 binary using Havard Eidnes's bootstrap scripts. Bootstrapping the initial VM running is sufficiently involved that I would recommend using my binaries (openjdk7-macppc-2009-12-16-b4.tar.bz2).
OpenJDK uses Mercurial with the Forest extension. Before checking out the BSD sources, you will need to install and configure Mercurial. See the OpenJDK Developer's Guide for more information.
To check out the BSD-Port forest:
hg fclone http://hg.openjdk.java.net/bsd-port/bsd-port
Building OpenJDK requires Java 6 or OpenJDK 7 -- on PPC, you will need to download or build an OpenJDK 7 bootstrap VM (openjdk7-macppc-2009-12-16-b4.tar.bz2).
To build the JDK in build/bsd-ppc/j2sdk-image:
make \ CC=gcc-4.0 \ CXX=g++-4.0 \ ALT_BOOTDIR=/usr/local/openjdk7-macppc-2009-12-16-b4 ANT_HOME=/usr/share/ant \ ALT_FREETYPE_HEADERS_PATH=/usr/X11/include \ ALT_FREETYPE_LIB_PATH=/usr/X11/lib \ ALT_CUPS_HEADERS_PATH=/usr/include \ ALT_CACERTS_FILE=/System/Library/Frameworks/JavaVM.framework/Home/lib/security/cacerts \ LIBFFI_CFLAGS="-I/usr/include/ffi" \ NO_DOCS=true \ ZERO_BUILD=true \ ZERO_ENDIANNESS=big \ ZERO_LIBARCH=ppc \ ZERO_ARCHDEF=PPC \ ZERO_ARCHFLAG=-m32
Be sure to set ALT_BOOTDIR to the path of your installed openjdk7-macppc-2009-12-16-b4 bootstrap JDK.
Once built, you should now have a JDK in build/bsd-ppc/j2sdk-image:
landonf@onefish:~/openjdk-ppc/bsd-port> ./build/bsd-ppc/j2sdk-image/bin/java -version openjdk version "1.7.0-internal" OpenJDK Runtime Environment (build 1.7.0-internal-landonf_2009_12_16_12_54-b00) OpenJDK Zero VM (build 17.0-b05, interpreted mode)
For more information or assistance, please refer to the OpenJDK BSD-Port wiki and mailing list. My testing has been very limited -- if you run into issues, please report them on the development mailing list.
As part of the OpenJDK project, various organizations have been working on OpenJDK 6, a freely distributable Java 6 implementation based on the open source OpenJDK 7 code base. Most Linux distributions are now shipping OpenJDK 6 binaries.
Soylatte (Java 6 Port for Mac OS X) was originally based on the BSD port of the JRL licensed Java 6 code base, which significantly constrains end-user usage and distribution rights. With Sun's approved re-licensing of the BSD changes for use in OpenJDK 7, a backport to OpenJDK 6 was made possible.
I've added support for OpenJDK 6 on Mac OS X, based on Brian Gardner's work backporting the OpenJDK 7 BSD changes to OpenJDK6/FreeBSD. Unlike the legacy Soylatte builds, OpenJDK 6 is:
The initial beta release is available for testing via the MacPorts openjdk6 port (Leopard only), or as a binary from the Soylatte web page (Leopard/Tiger, untested on Tiger). My ability to provide 10.4 support is constrained without access to a 10.4 machine, and any testing/development assistance is most welcome.
To help facilitate user testing, I've built and posted OpenJDK 7 binaries for Mac OS X 10.5 here:
openjdk7-darwin-i386-20080820.tar.bz2 (sig).
Help us find bugs -- give your code a try on OpenJDK 7, and send any issues along to the bsd-port-dev mailing list.
The long-term goal of the SoyLatte project was to ensure open, timely development of Java 7 for Mac OS X, with support for all recent versions of Mac OS X.
I'm pleased to announce that OpenJDK 7 is now runnable on both Mac OS X and the BSDs, as part of the OpenJDK BSD Port. The project represents the culmination of considerable work by Greg Lewis, Kurt Miller, Dalibor Topic, and myself.
landonf@max> uname -s -r Darwin 9.4.0 landonf@max> ./build/bsd-i586/j2sdk-image/bin/java -version openjdk version "1.7.0-internal" OpenJDK Runtime Environment (build 1.7.0-internal-landonf_2008_08_19_12_38-b00) OpenJDK Server VM (build 14.0-b01, mixed mode)
OpenJDK uses Mercurial with the Forest extension. Before checking out the BSD sources, you will need to install and configure Mercurial. See the OpenJDK Developer's Guide for more information.
To check out the BSD-Port forest:
hg fclone http://hg.openjdk.java.net/bsd-port/bsd-port
As an alpha port to an in-development code base, some bugs are to be expected. Testers are most welcome.
Due to bugs in 10.4's compiler, building the sources currently requires a Mac OS X 10.5 machine.
Bootstrapping OpenJDK currently requires either SoyLatte 1.0.3, or a binary release of OpenJDK. The code base will not bootstrap against Apple's JVM.
Some portions of OpenJDK are still unavailable under an open-source license. To build OpenJDK, will also need Kurt Miller's binary plugs for the BSD port: jdk-7-icedtea-plugs-1.6.tar.gz. The binaries are derived from the IcedTea project.
To build the JDK in build/bsd-i586/j2sdk-image:
make \ ALT_BOOTDIR=/usr/local/soylatte-i386-1.0.3 ALT_BINARY_PLUGS_PATH=$HOME/jdk-7-icedtea-plugs \ ALT_FREETYPE_HEADERS_PATH=/usr/X11R6/include \ ALT_FREETYPE_LIB_PATH=/usr/X11R6/lib \ ALT_CUPS_HEADERS_PATH=/usr/include \ ANT_HOME=/usr/share/ant \ NO_DOCS=true \ HOTSPOT_BUILD_JOBS=1
Please make sure you're using the latest SoyLatte 1.0.3 release, or the build will fail.
The move to OpenJDK -- and Sun's re-licensing of the code under the GPL license -- opens the project to any interested contributer. Some exciting areas of exploration:
If you're interested in contributing, please join the bsd-port-dev mailing list.
With Sun's approval to merge the BSD Java patchset to OpenJDK squared away, Dalibor Topic has proposed project sponsorship for the BSD porting project.
If sponsorship is approved, the BSD Java Port -- which includes all the Soylatte changes -- will be able to officially join the OpenJDK project. Combined with the zero-assembler port, we could see OpenJDK 6/7 support in the near future for Mac x86 and PPC machines.
I'm very excited to see OpenJDK/BSD support progressing due to the hard work Dalibor Topic, Greg Lewis, and Kurt Miller.
Over the weekend I implemented an initial port of Java 6 to FreeBSD/Sparc64, primarily as a learning exercise -- I wanted to see how difficult it is to port Java to a platform where both the processor and operating system are already independently supported.
landonf@conpanna:bsd-sparc> uname -s -m FreeBSD sparc64 landonf@conpanna:bsd-sparc> ./bin/java -server Hello Hello, World
I believe this is the first port of the Sparc JVM to a non-Solaris system, and the work should be applicable to supporting other operating systems, such as NetBSD or Linux Sparc systems. This article will discuss the steps I took, with the hope of aiding future porters.
The JRL-licensed code can be downloaded here: patch-java6-freebsd-sparc-1.gz
By downloading these binaries or source code, you certify that you are a Licensee in good standing under the Java Research License of the Java 2 SDK, and that your access, use, and distribution of code and information you may obtain at this site is subject to the License. To ensure compliance, downloading requires "click-through" authentication:
Building Java requires Java, which is a catch-22 when you're bootstrapping an unsupported system. To work around this, I used an idea (and scripts) suggested by Havard Eidnes: I set up a second Linux system running Sun's Java, and then mounted my FreeBSD build directory at the exact same path on the Linux machine.
Havard's scripts ssh to the bootstrap host and run the Java commands there. The source files are read from the NFS build tree, and the output files are written back.
You can download my slightly modified version of Havard's scripts here: boot-java.tar.gz. Any bugs are surely my own. To use the scripts, set the following environmental variables:
When calling make, you must also ALT_BOOTDIR to the boot-java path (eg, $HOME/boot-java).
I started by running 'make' and filling in the blanks -- the best approach is to copy liberally from existing platform implementations. In most cases, I borrowed the solaris-sparc implementation, and merged in code from the bsd-amd64 counterpart:
Nearly all the new code needed to be added to hotspot/src/os_cpu/bsd_sparc. I took an iterative approach, starting from the Solaris code, merging in BSD-specific code, and attempting to build the result. Except for the slow machine I was working with (400Mhz!), merging in the BSD code was a fairly swift process.
Sun builds the Solaris VM using the Sun Studio toolchain, which is not fully compatible with GCC. I had trouble with gcc 3.4, and eventually settled on 4.0, which worked almost perfectly, barring three issues.
First, gcc defines 'sparc' as a standard preprocessor macro. You can guess how well that works while compiling a sparc-related code; passing the -ansi flag disables the define.
Secondly, gcc does not support passing non-const objects as a reference parameter, while Sun Studio allows it. Relying on this is non-standard, but easily fixed -- see 'Reference to a non-const object cannot be initialized with an r-value of that object'.
Lastly, I had to rewrite the Sun Studio inline assembler template (hotspot/src/os_cpu/solaris_sparc/vm/solaris_sparc.il) in standard assembler. A good discussion of the differences between Studio's inline assembler and GCC-style assembly can be found at Alfred Huang's blog. This was straight-forward -- here's an example:
.inline _Atomic_swap32, 2 .volatile swap [%o1],%o0 .nonvolatile .end
Re-written as:
.global _Atomic_swap32 .align 32 _Atomic_swap32: swap [%o1],%o0 retl nop
I'm happy to announce another update for SoyLatte, containing a number of minor improvements. Work also progresses on the feature branch, where I'm focusing on native graphics support.
Bug fixes:
Binaries, source, build, and contribution instructions are all available from SoyLatte Project Page
When implementing a virtual machine such as Java's, it's necessary (and sometimes beneficial) to handle some unexpected conditions by allowing the errors to occur, and then catching the resultant signals delivered by the operating system. Take, for example, divide by zero:
int i = 5 / 0;
Hotspot could generate code to check divisor == 0 before every division operation:
cmpl $0, %ecx // Is the divisor 0 je L2 // Jump to div-by-zero handler movl %edx, %eax // store in divisor eax sarl $31, %edx // clear edx, leaving the sign bit idivl %ecx // divide edx:eax / ecx
But instead, Hotspot takes a leap of faith -- since programs should rarely divide by zero, Java emits the division instruction, and if the divisor is 0, relies on its signal handler to interpret the resultant SIGFPE:
if (sig == SIGFPE && (info->si_code == FPE_INTDIV || info->si_code == FPE_FLTDIV)) { stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime::IMPLICIT_DIVIDE_BY_ZERO);
On Friday, I received a bug report for the x86_64 version of SoyLatte from Jibril Gueye. As it turns out, divide by zero errors were not being handled in the 64-bit VM, and instead of throwing an ArithmeticException, Java was unceremoniously crashing:
landonf@max> /usr/local/soylatte16-amd64-1.0.1/bin/java Test # # An unexpected error has been detected by Java Runtime Environment: # # SIGFPE (0x8) at pc=0x0000000101886ba8, pid=35000, tid=0x301000
After fixing the issue, I thought it would be interesting to discuss how Java handles signals, and why the SIGFPE handler didn't work:
After the JVM has parsed its command line arguments, the os::init_2() operating-specific method is called. This method is responsible for performing any remaining OS-specific initialization tasks, such as the registration of signal handlers. The BSD implementation can be found in hotspot/src/os/bsd/vm/os_bsd.cpp.
At this time, an architecture-specific JVM_handle_bsd_signal() function is registered as a handler for SIGSEGV, SIGPIPE, SIGBUS, SIGILL, and SIGFPE. (See signal.h for descriptions.) When a divide by zero error occurs, SIGFPE is delivered to the process, and the JVM's JVM_handle_bsd_signal() is called.
The signal handler is registered using sigaction, with the SA_SIGINFO flag set. According to the Single Unix Specification, "If SA_SIGINFO is set and the signal is caught, the signal-catching function will be entered as:"
void func(int signo, siginfo_t *info, void *context);
Upon a divide by zero, the provided siginfo structure contains a 'si_code' member set to FPE_INTDIV:
typedef struct __siginfo { int si_signo; /* signal number */ int si_errno; /* errno association */ int si_code; /* signal code */ ... } siginfo_t;
With this information, our Java_handle_bsd_signal() implementation can check the signal number and code, and throw an ArithmeticException:
if (sig == SIGFPE && (info->si_code == FPE_INTDIV || info->si_code == FPE_FLTDIV)) { stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime:: IMPLICIT_DIVIDE_BY_ZERO);
SharedRuntime::continuation_for_implicit_exception() returns the entry point to Hotspot-generated code that sets up Java exception dispatching in the current frame. When the signal handler is finished, it saves the program counter and jumps to this stub, which handles setting up the frame and throwing the ArithmeticException.
After receiving the bug report, I decided to take a look at Mac OS X's kernel signal handling code. On Darwin, the sendsig function handles creation and dispatch of UNIX signals to user processes. Looking at sendsig, we see that Mac OS X doesn't set si_code to FPE_INTDIV, and as such, JVM_handle_bsd_signal() can't decipher the signal:
case SIGFPE: #define FP_IE 0 /* Invalid operation */ #define FP_DE 1 /* Denormalized operand */ #define FP_ZE 2 /* Zero divide */ #define FP_OE 3 /* overflow */ #define FP_UE 4 /* underflow */ #define FP_PE 5 /* precision */ if (ut->uu_subcode & (1 << FP_ZE)) { sinfo64.si_code = FPE_FLTDIV; } else if (ut->uu_subcode & (1 << FP_OE)) { sinfo64.si_code = FPE_FLTOVF; } else if (ut->uu_subcode & (1 << FP_UE)) { sinfo64.si_code = FPE_FLTUND; } else if (ut->uu_subcode & (1 << FP_PE)) { sinfo64.si_code = FPE_FLTRES; } else if (ut->uu_subcode & (1 << FP_IE)) { sinfo64.si_code = FPE_FLTINV; } else { printf("unknown SIGFPE code %ld, subcode %lx\n", (long) ut->uu_code, (long) ut->uu_subcode); sinfo64.si_code = FPE_NOOP; } break;
As you can see, there's no code to handle FPE_INTDIV, si_code is set to FPE_NOOP, and an error message is printed to the console. A quick check of dmesg shows that our kernel is indeed printing "unknown SIGFPE" when Java attempts a divide by zero:
sudo dmesg | grep SIGFPE unknown SIGFPE code 1, subcode 0
This is suboptimal behavior, so I've filed a bug (5708523 - xnu sendsig() does not set siginfo->si_code = FPE_INTDIV for SIGFPE). In the meantime, a fix is necessary.
You may recall the 'void *context' argument passed to the signal handler. On Mac OS X, this is actually a pointer to ucontext structure. The ucontext contains the full context of the thread's state, at the time of the exception. This includes the program counter -- a register containing the address of the instruction that caused the exception.
Since we have the address of the instruction, we can determine what the instruction is. Once we know what the instruction is, we determine if it could have caused an integer divide by zero exception. This fix was used previously in Java to support Linux/x86 1.x kernels, which also did not set si_code.
To determine what instruction(s) could cause a FPE_INTDIV on 64-bit x86 machines, I consulted the Intel 64 and IA-32 Architectures Software Developer's Manuals -- the answer is idiv and idivl. Also, on amd64 machines, most operations remain 32-bit, and 64-bit operations require the a REX prefix. We'll need to skip the prefix if it exists.
Now we can add code to examine the program counter in JVM_handle_bsd_signal():
// HACK: si_code == FPE_INTDIV is not supported on Mac OS X (si_code is set to FPE_FPE_NOOP). // See also xnu-1228 bsd/dev/i386/unix_signal.c, line 365 // Filed as rdar://5708523 - xnu sendsig() does not set siginfo->si_code = FPE_INTDIV for SIGFPE } else if (sig == SIGFPE && info->si_code == FPE_NOOP) { int op = pc[0]; // Skip REX if ((pc[0] & 0xf0) == 0x40) { op = pc[1]; } else { op = pc[0]; } // Check for IDIV if (op == 0xF7) { stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime:: IMPLICIT_DIVIDE_BY_ZERO); } else { // TODO: handle more cases if we are using other x86 instructions // that can generate SIGFPE signal. tty->print_cr("unknown opcode 0x%X with SIGFPE.", op); fatal("please update this code."); }
With the fix in place, Java throws the expected ArithmeticException:
landonf@max:~> java Test Exception in thread "main" java.lang.ArithmeticException: / by zero at Test.main(Test.java:3)