Running Gambit Scheme on iOS

 

I used Gambit Scheme to build my iOS game Cloud Breaker. Here are the steps you can take to get Gambit up and running on your iPhone or iPad. I assume you have the latest version of Xcode installed (I have Xcode 5.1.1).

I originally used jlongster's post to get started. It has a lot of good information as well, but some things have changed since that post was written. My goal with this guide is to give a more up-to-date overview (until everything changes again).

Overview

Gambit Scheme is a compiler that translates Scheme code into C code. The generated C code can then run on just about anything (iPhones and iPads in this case), but we also need to link that C code to a build of the Gambit Runtime, libgambc.a, which handles garbage collection, standard library calls, etc.

Xcode is happy to compile the generated C code just like any other source files, but we'll build the runtime library separately so that we don't have to rebuild it after a project clean, and so that we can link it to multiple independent projects.

Step 1: Get the Gambit Source

$ git clone https://github.com/feeley/gambit.git

Step 2: Build the Gambit Compiler for Your System

We want a build of Gambit for our system to get gsc and gsi, the compiler and the interpreter, so that we can compile our Scheme code into something the device can run. It's also handy to be able to interpet the Scheme code on our system, which is faster for building and testing than pushing to the device every time.

Gambit compiles faster and generates a faster runtime with recent gcc versions (versus the gcc that Apple provides which uses LLVM). So before building Gambit, I installed gcc 4.8 via homebrew.

$ brew install gcc48

Okay, now let's build Gambit using our new compiler.

$ cd gambit
$ CC=/usr/local/bin/gcc-4.8 ./configure --enable-single-host
$ make -j4 from-scratch
$ make check
$ sudo make install
$ gsc -v
v4.7.2 20140411185957 x86_64-apple-darwin13.1.0 "./configure '--enable-single-host' 'CC=/usr/local/bin/gcc-4.8'"

The -j4 option tells make to use 4 simultaenous jobs, which is the number of CPU cores on my system. Modify this as necessary.

After this we have a Gambit compiler (gsc) and intepreter/REPL (gsi).

$ gsi
Gambit v4.7.2
> (+ 1 2)
3
> ,q

Step 3: Build the Gambit Runtime Library

You need to build the Gambit runtime library for each architecture you want to run on. I do armv7 (iPhone 3GS through iPhone 4S), armv7s (iPhone 5 and later), and i386 (for the Simulator). We'll pack them all into one library for convenience sake.

I believe that the newer devices will run armv7 code, but the armv7s code should run faster.

I recommend using my build script instead of the current one in the main repo. There are some changes for compatibility with the latest Xcode versions and some other tweaks that should save some build time.

$ wget https://raw.github.com/asivitz/gambit/master/misc/build-gambit-iOS -O misc/build-gambit-iOS
$ cd misc
$ sh build-gambit-iOS --no-update -j 4

The script builds the two arm architectures and the i386 arch, and packs it into a library. This takes a long time (over a couple hours on my laptop). The reason it's slow is that we have to use Apple's LLVM-based compilers to build the ARM runtimes.

The --no-update flag stops the script from updating the downloaded distribution to HEAD, and instead builds from the latest release version.

-j 4 specifies the number of make jobs, again, equal to my CPU cores.

Anyway, once it's done, check it to make sure it has all your desired architectures:

$ file gambit-iOS/current/lib/libgambc.a
libgambc.a: Mach-O universal binary with 3 architectures
libgambc.a (for architecture i386):i386     current ar archive random library
libgambc.a (for architecture armv7):armv7   current ar archive random library
libgambc.a (for architecture armv7s):armv7s current ar archive random library

Step 4: Link the Gambit Library to Your App

In your Xcode project, go to your build settings and find the Search Paths section.

Add a library search path...

Library Search Paths        PATH_TO_GAMBIT/misc/gambit-iOS/current/lib

And a user header search path...

User Header Search Paths        PATH_TO_GAMBIT/misc/gambit-iOS/current/include

In the Linking section, add a linker flag for the gambit library.

Other Linker Flags        -lgambc

Since we're embedding the gambit runtime into our app, we need to tell gambit not to generate a main function. So we need to define ___LIBRARY. In my Xcode 5.1, you do that in the Apple LLVM 5.1 - Preprocessing section.

Preprocessor Macros Not Used In Procompiled Headers        ___LIBRARY

Step 5: Write some Scheme Code

Here's a good starting point. Save it in a file called "init.scm" in your project's source folder.

; pure Scheme call
(println "We're here in Scheme land!")

; Scheme function callable from C
(c-define (c-add-five num) (int) void "add_five" ""
   (+ 5 num))
   
; C function callable from Scheme
(define nslog
  (c-lambda (char-string)
            void
            "
            NSString * str = [NSString stringWithCString:___arg1 
                                 encoding:NSASCIIStringEncoding];
            NSLog(@\"Logging: %@\", str);
            "))
            
(nslog "Hello Obj-C World!")

Step 6: Compile the Scheme code into C

Although we want to be able to run this code on ARM, the Gambit compiler produces the exact same C code no matter what platform the compiler is run on. So we'll use the normal system compiler.

Compile each of your Scheme files into .m files. (Right now you just have one.)

# From your project's source folder, where you put init.scm
$ gsc -c -o init.m init.scm
$ ls init*
init.m init.scm

Create a link file out of your new .m file(s).

$ gsc -link -o init_.m init.m
$ ls init*
init.m init_.m init.scm

Go into Xcode. Add init.m and init_.m to your project. Make sure they are added to your target, or else they won't get compiled in.

Step 7: Execute the Gambit Runtime in Your App

Add this code to your ViewController.m class above its implementation. Be sure to match the ___VERSION define with your Gambit scheme version.

/* These function defs match what is declared in your scheme code. */
void add_five(int num);

/* This version number must match what's written in include/gambit.h.in in
 * the gambit source distribution. 407002 is the version number for Gambit 4.7.2 */
#define ___VERSION 407002
#include "gambit.h"

/*
 * Define SCHEME_LIBRARY_LINKER as the name of the Scheme library
 * prefixed with "____20_" and suffixed with "__".  This is the
 * function that initializes the Scheme library.
 * I use init because my main scheme file is called init.scm
 */
 
#define SCHEME_LIBRARY_LINKER ____20_init__

___BEGIN_C_LINKAGE
extern ___mod_or_lnk SCHEME_LIBRARY_LINKER (___global_state_struct*);
___END_C_LINKAGE

void gambit_setup()
{
   /*
    * Setup the Scheme library by calling "___setup" with appropriate
    * parameters.  The call to "___setup_params_reset" sets all
    * parameters to their default setting.
    */
    
   int debug_settings = ___DEBUG_SETTINGS_INITIAL;
   
   ___setup_params_struct setup_params;
   
   ___setup_params_reset (&setup_params);
   
   setup_params.version        = ___VERSION;
   setup_params.linker         = SCHEME_LIBRARY_LINKER;
   setup_params.debug_settings = debug_settings;
   
   ___setup (&setup_params);
}


void gambit_cleanup()
{
   ___cleanup ();
}

Add gambit_cleanup() to your dealloc method. [super dealloc] omitted because I assume you're using ARC.

- (void)dealloc
{
   ...
   gambit_cleanup();
}

Add gambit_setup() to your viewDidLoad method

- (void)viewDidLoad
{
    [super viewDidLoad];
    ...
    gambit_setup();
}

Step 8: ???

We're ready to go! Build and run the app and celebrate!

Or curse the errors you're getting and ping me on twitter.

Step 9: Profit!

Stay tuned for some follow-up posts on the use of FFI (foreign function interface) to integrate your Scheme code with C/Obj-C, and some other tips on working with Gambit on mobile. I'll also tackle the Android install process.