UPDATE: This is inferior technology...
see here for the new way!
Ok.. this is WAY off-topic, even more so than my last couple of posts. I promise that I'll get back to the rocket business eventually, but I just think this is too cool not to share. Plus I wanted to record what I did somewhere so I don't forget about it. Not to mention the amount of nerd street cred that something like this is worth...
In a nutshell, I've managed, through sheer bullheadedness, to get something that is completely useless to 99.44% of android phone owners. Something that seemed perfectly reasonable to me that was just absent... a Fortran compiler.
A little backgroud...
When I got my shiny new Verizon Droid back in June of last year, I was really excited by the possibilities that the platform presented to me with its open source Linux based Android goodness under the hood. Unfortunately, I quickly discovered that while google has indeed released the source for the Android platform, that platform is lacking some of the things that make life on planet Unix so fun and portable. Most notable of these are the
X11 windowing system and the full
GNU Compiler Collection, lovingly referred to as GCC. Those two major omissions, along with their decision to use a
non-standard c library, mean that it is far from trivial to get applications ported over. Ironically, this is exactly what makes the far less open Apple mobile iOS much easier to develop for (from what I've read anyway), since most of the facilities from the desktop (OSX) are available in some fashion or other and everything is standardized and collected neatly together under the
Xcode IDE. The google answer to this was to add a bunch of features to the venerable Eclipse IDE, declare that Java will be the language of choice for the platform and called it good.
Now as someone who has sworn off Java for life on the grounds (small pun intended) that it is a language with all the complexities of C++ and none of the speed, I just refuse to stand by while the company that will one day bring us Skynet attempts to keep me from using my phone the way I want. I actually had high hopes for the so-called
scripting layer for android, which makes it possible to run Python (my second favorite computer language) on the phone. But once again, google has seen fit to hamstring developers by only providing bindings to a handful of the phone's low level functions, relegating Python and friends to second class citizens in the Android kingdom.
This is where my Odyssey began, a simple desire to do what I wanted on my phone in a language that I understand.. that language being Fortran. Now you might ask me, "Mike, what place does the oldest surviving computer language have on my shiny new personal communication device?". Well, my answer to that is maybe it doesn't, but I think it should get a chance, don't you? Fortran is still the undisputed heavyweight number crunching champion of the world, so why not try to take advantage of that to write some smoking fast applications for the little black phone that could. You might also say, "What's the point since the android API is not accessible from Fortran at all so it's even less useful than Python?". To that, my answer is the magical Fortran fairy dust called
"iso_c_binding". Since the
Android NDK provides interfaces to most of the phone's functions via C, then I should be able to, at least in theory, access those functions from Fortran by wrapping them in C.
So, with this knowledge in hand, I proceeded to download the latest GCC 4.6 pre-release source code and started looking online for information that I needed to build a cross compiler for the ARM CPU in my phone... two weeks and a few gray hairs later, I marveled at the wonder of 'Hello World' in three lines of Fortran running in a terminal on my droid.
Here's how I did it... but be warned, what follows is full of dirty hacks, workarounds and generally poor execution, so don't expect it to work for you the first time if you attempt to duplicate it. I will, however, try to re-create the process as faithfully as possible.
The first thing you will need is a computer running Linux, in my case
I am using Fedora 13, but most other distributions should work without major changes. Don't even think about asking me how to do this under Windows unless you want to be taunted mercilessly until you have no choice but to abandon this blog altogether... seriously. It won't work under Windows so don't try it.
The next thing you need is a functional Android SDK and NDK environment with the system ABI resources for your phone. I leave installation of that as an
exercise for the reader. Once you have that, there is a handy script in the NDK under {ndk-root}/build/tools called make-standalone-toolchain.sh that does just what its title implies, which is to create a "standalone" compiler for the desired Android ABI level, in my case, level 8, which corresponds to Android 2.2, yummy, yummy,
Froyo. After running the script, add the new compilers to your path, and then congratulate yourself on having a fully functional Android C compiler and a somewhat less functional C++ compiler.
The remaining steps I'll just lay out by example because that will hopefully make it easier to follow and it's getting late and I want to go to bed.
Make one small change to the linux headers in the Android NDK. In the file
{path-to}/android-ndk-r5/platforms/android-8/arch-arm/usr/include/linux/stat.h
Add the following two lines:
#define S_IREAD S_IRUSR
#define S_IWRITE S_IWUSR
just below the line that says:
#define S_IWUSR 00200
This fixes a dependency that gfortran has on a couple of deprecated symbols that have been removed in the NDK header files.
Make a directory to build all of the pieces:
(you can skip building gmp, mpfr and mpc if you have the devel packages for your platform installed already)
mkdir /opt/compilers/gcc/build
cd /opt/compilers/gcc/build
#Download and build GMP
wget http://ftp.gnu.org/gnu/gmp/gmp-4.3.2.tar.gz
tar zxvf gmp-4.3.2.tar.gz
mkdir gmp-build
cd gmp-build
../gmp-4.3.2/configure --prefix=/opt/compilers/gcc/android-8 --enable-cxx
make && make install
#Download and build MPFR
wget http://ftp.gnu.org/gnu/mpfr/mpfr-2.4.2.tar.gz
tar zxvf mpfr-2.4.2.tar.gz
mkdir mpfr-build
cd mpfr-build
../mpfr-2.4.2/configure --prefix=/opt/compilers/gcc/android-8 --with-gmp=/opt/compilers/gcc/android-8
make && make install
#Download and build MPC
wget http://www.multiprecision.org/mpc/download/mpc-0.8.1.tar.gz
tar zxvf mpc-0.8.1.tar.gz
mkdir mpc-build
cd mpc-build
../mpc-0.8.1/configure --prefix=/opt/compilers/gcc/android-8 --with-gmp=/opt/compilers/gcc/android-8 --with-mpfr=/opt/compilers/gcc/android-8
make && make install
#Download and build GNU Binutils 2.19.1 for the android target machine
(newer versions will probably work just fine, maybe better)
wget http://ftp.gnu.org/gnu/binutils/binutils-2.19.1.tar.gz
tar zxvf binutils-2.19.1.tar.gz
mkdir binutils-build
cd binutils-build
../binutils-2.19.1/configure --prefix=/opt/compilers/gcc/android-8 --with-gmp=/opt/compilers/gcc/android-8 --with-mpfr=/opt/compilers/gcc/android-8 --with-mpc=/opt/compilers/gcc/android-8 --target=arm-android-eabi --with-sysroot={path-to}/android-ndk-r5/platforms/android-8/arch-arm
make && make install
Download the EXACT version of the GCC source that I used:
svn co svn://gcc.gnu.org/svn/gcc/trunk@168097 gcc-trunk
Download this patch and apply it to the gcc-trunk...
cp ugly_gfortan_hacks.patch gcc-trunk/.
cd gcc-trunk
patch -p0 < ugly_gfortran_hacks.patch
With any luck this should get your GCC source tree into the same sad state that mine is.. sad, but functional!
Now move back out of the GCC source directory and create a new directory in which to build the compilers. If you've ever built GCC before, you know it is a very bad idea to build it in it's own source tree.
cd ..
mkdir build-android-8
cd build-android-8
Now you need to configure and build the compiler suite. This is the part that really took a lot of head scratching and googling since I had never built a 'cross' compiler before. I'll save you the pain of trying to explain what all of this means, so just copy and paste these lines, fix the {path-to} your Android NDK and you should be off to the races.
../gcc-trunk/configure --prefix=/opt/compilers/gcc/android-8 --disable-libquadmath --target=arm-android-eabi --with-gnu-as --with-gnu-ld --enable-languages=c,fortran --with-mpfr=/opt/compilers/gcc/android-8 --with-gmp=/opt/compilers/gcc/android-8 --with-mpc=/opt/compilers/gcc/android-8 --disable-libssp --enable-threads --disable-nls --disable-libgomp --disable-shared --disable-tls --with-float=soft --with-fpu=vfp --with-arch=armv5te --enable-target-optspace --disable-nls --with-sysroot={path-to}/android-ndk-r5/platforms/android-8/arch-arm
make && make install
This could take an hour or more to complete, but you can speed things along in the typical 'make' fashion by using a parallel build (assuming you are on a multicore box).. i.e.. on a quad machine..
make -j 4 && make install
Assuming you get this far, you can now compile and assemble code for Android, but sadly it will not run on the phone quite yet. This is where I nearly threw in the towel since I could get Fortran code to build, but it would
segfault immediately if I tried to run it.. and when I say immediately, I mean before executing even a single line. After a great deal of effort I was ready to accept that I'm more of a code plumber than a trained programmer and that some tiny piece of the puzzle was beyond my grasp. That's when the engineer in me spoke up and said.. hold on, just take a step back, leave it alone for a day and try again tomorrow.
That next day I decided to try to get a better handle on what was going on behind the scenes with the compiler. To do this, I found a simple c program on the internet (btw.. it's amazing how many ways you can calculate the value of pi) and proceeded to build it with both the Android supplied and my newly minted c compilers and push it to the phone.
NDK compiler = success. New compiler = squat.
Now I broke things down a little further. I'm by no means an authority on compilers, but I did know that there are a number of stages that code goes through on it's journey from human readable (some languages less than others) to machine readable. These steps being:
- Compile
- Assemble
- Link
I also knew that while all of these steps often appear to be taken by the single compiler executable (gcc, g++, gfortran, etc.), in reality, the compiler hands off the latter two steps to the 'assembler' and the 'linker'... sneaky little basterd.
This is where things get interesting... Not really expecting to get anywhere, I decided to start mixing and matching the components from the NDK with my new compiler...
- NDK compiler + NDK assembler + NDK Linker ==> works.
- New compiler + New assembler + New Linker ==> no good.
- NDK compiler + NDK assembler + New Linker ==> no good.
- NDK compiler + New assembler + New Linker ==> no good.
- New compiler + New assembler + NDK Linker ==> WORKS!
Ok.. so that's the key. For some reason, the new linker is the weak.. uh. link.
After spending some time trying to reverse engineer the linker script used by the NDK linker, I just said forget it, I have something that works. Luckily,
GNU 'makes' (wow, the puns just keep coming..) it really easy to use any linker you want when building code.. so, without further ado...
No, I didn't do the interface bits in Fortran, that's one thing Python IS good for on Android, at least for simple things like this... that and making it talk. FYI.. the Linpack for Android app available on the market only gets about 8 million floating point operations per second on my phone, which is close to what I got with the default compiler options. The 25 MFLOPS shown in the video was after optimizing for the Neon floating point unit in the Droid's
Cortex-A8 processor. Makes me wonder how many other applications out there are not making very good use of the hardware.
Well. There's that then.
Longest post ever.