How Red Hat ported OpenJDK to 64-bit Arm: A community history
It has been quite a year for Arm Ltd., the firm that designs reduced instruction set computing (RISC) architectures for computer processors. The news that Arm-based computers will be important for the foreseeable future has even reached the mainstream media. At the end of 2019, Amazon Web Services announced Arm-based Graviton2 servers. In June 2020, Apple announced its plans to move Macintosh computers over to Apple silicon—which means Arm.
For those of us who have worked for years on Arm servers, this shift has been a long time coming.
In the beginning
Wind back to a day in 2011. I was having lunch at The Wrestlers (the Cambridge U.K. tech community’s favorite Thai eatery) with Jon Masters, Red Hat’s lead Arm architect. He had exciting news to share: Arm would produce a 64-bit architecture, and Red Hat was going to port Red Hat Enterprise Linux (RHEL) to it. We’d need to solve quite a few problems to get this done, but one was particularly difficult. The software then available for the new 64-bit architecture did not include Java. Java is a key component of much enterprise software, so this news was a pretty big deal.
Someone would have to write a port. I had heard that two experts could write a bare-bones port of OpenJDK in about a year, but that was all I knew. If my team were going to do it, we’d have to learn on the job.
I was thrilled at the prospect of getting my teeth into such a substantial piece of work, but how could Red Hat justify starting this big project with no guarantee of success? I argued that unless we did it, we’d have to pay someone. That would cost lots, so why not save money by doing it ourselves? It would be good publicity, and we’d be able to build alliances. But there was another, deeper reason to keep it in-house. We’d been building up a support team for OpenJDK. By writing an entire port, we’d gain experience that we could not get any other way. Red Hat’s management agreed (and has consistently supported the project ever since), so we were good to go.
I was determined to be one of the engineers on this project, but I couldn’t do it alone. Fortunately, Andrew Dinn was about to join our Java team. He’d worked on Java for a long time and had experience writing compilers for Lisp machines and logic languages. He’d be a good fit.
One thing worried me: What if someone else did a port first? Then the whole effort might be wasted, along with any hope of glory. The only way to prevent that disaster was to gain first-mover advantage and do the work in public. Get it done fast, do it well, and make it free. Build it, and they will come.
Starting the project
We did have one problem—or rather, two. First, there was no AArch64 processor in existence. Also, persuading Arm Ltd. to give us the detailed information we needed was not easy. Persistence and help from Arm’s Philippe Robin solved the documentation problem, but the lack of hardware was more difficult. It is possible to run all of OpenJDK under simulation, but the simulators at the time were painfully slow. What’s worse, OpenJDK has to call out to the operating system. We would need to bootstrap all of Linux on the simulator before we could fire up Java.
Andrew Dinn remembers us being at breakfast at a conference when I excitedly told him that, while in the shower that morning, I’d figured out what to do. We’d use a simple, functional instruction set simulator, but just for the AArch64 code that we generated ourselves. The rest of the OpenJDK JVM is C++. We could run that natively at full speed on an Intel x86-based PC. Every call from C++ to Java would enter the simulator, and every call from Java back to C++ would leave the simulator and return to x86 code. But where would we get an AArch64 simulator library? “We’ll write one,” I said. “It’s a RISC. How hard can it be?”
We started in June of 2012. It took Andrew Dinn a little while to write the simulator, while I got on with writing the assembler and initial startup code. After a couple of months, we were executing Java bytecodes. The idea of writing our own simulator had been inspired. We could create complex breakpoint conditions and even record instruction traces so that we could see the instruction history when the JVM crashed. As a result, the initial porting went quickly. Looking back at the logs, I see an entry of “Enough for Hello, World!” on Oct 4, 2012. This was an important day: To get as far as outputting “Hello, World!” to the console, Java has to execute about three-quarters of a million bytecodes without crashing, and exercise much of the virtual machine.
And then there were three
By the summer of 2013, three of us were working on the project. Edward Nevill, a Java expert formerly of Arm Ltd., joined the project from Linaro. He was the first to get actual hardware. I was so comfortable with our little AArch64 simulator that I was very happy for someone else to debug our work on the real machine. I expected it to be a painful process. But, apart from a small problem with flushing the instruction cache and an issue with floating-point flags, it all worked! I was astonished. We’d written our own simulator, compiler, and assembler from Arm’s documentation. We’d had no independent verification, but we’d got it right.
Edward was a tremendous help, and by the start of 2014, we had a working port. Red Hat began shipping early releases to our partners, we and others carried on fixing bugs and improving performance, and on March 2, 2015, the AArch64 port was part of the main OpenJDK project. Oracle was great and helped and encouraged us through the tricky integration process.
Consolidation and the (very) long tail
There’s a considerable difference between a working JVM and one that’s really good. I don’t know how much time has been spent on Intel/x86 OpenJDK, but it must be dozens of engineer years. It was a real challenge for us to make the AArch64 port competitive, but we had help. People from the AArch64 silicon producers and other companies joined us, and now people from all around the world are working on the port.
This is where Red Hat’s open and collaborative approach really pays off. By forming partnerships with others, we gain a big multiplier over purely in-house software development.
But that’s not the whole story. We’d heard that Oracle was running Java on 64-bit Arm, and I wondered if it was our port. As it turned out, the port was proprietary, based on the (32-bit) Arm port Oracle already had. I was worried that Java’s originator would get much better performance than we had. Eventually, someone who had used Oracle’s port kindly assured me that I had nothing to worry about. I found it strange that Oracle wrote a port of its own, however. The company had permission from the beginning to use all of our code. Why write it again?
Eventually, Oracle freed both of its Arm 32- and 64-bit ports. That left us with two AArch64 ports in OpenJDK. Then, on November 12, 2020, Oracle announced that it had decided to focus its resources going forward on a single 64-bit Arm port: The AArch64 port we’d started at Red Hat. The 64-bit Arm support in Oracle’s maintenance release of JDK 8 is now based on our port, as well.
Recently, with much fuss, Apple announced the Apple M1 chip, an implementation of AArch64. OpenJDK was ready for that processor and had been for five years. However, there is a layer of OpenJDK that’s specific to each OS-CPU combination. Somebody had to do that work, and we soon had two volunteers: Microsoft and Azul, who worked on it together. Microsoft also ported the OS-CPU layer to Windows/AArch64.
So, why did all this happen? The OpenJDK developer community made the decision. I believe that’s because our little team stepped up with a working port early, got it into the OpenJDK mainline, and welcomed everyone who wanted to participate. We kept as many discussions as possible open. People volunteered, and we became a community. We achieved far more with this community than we ever could have hoped to do on our own. But none of it would have happened if we had not released that working port back at the start of 2014.