experiments/little-smalltalk/run.cc
author Tony Garnock-Jones <tonygarnockjones@gmail.com>
Sat, 04 Aug 2018 16:23:51 +0100
changeset 432 527bdc1042a9
parent 431 97c12bf829d8
child 433 73fb551bd557
permissions -rw-r--r--
Typo fix

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <ctime>

#include <string>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>

#include <netinet/in.h>

using namespace std;

typedef void *obj;
typedef intptr_t smi;

static inline int isSmi(obj o) { return ((intptr_t) o) & 1; }
static inline obj mkSmi(intptr_t v) { return (obj) ((v << 1) | 1); }
static inline smi unSmi(obj o) { return ((smi) o) >> 1; }

static inline intptr_t rawCount(obj o) { return ((intptr_t *) o)[0]; }
static inline int isBytes(obj o) { return rawCount(o) & 1; }
static inline unsigned slotCount(obj o) { return rawCount(o) >> 1; }
static inline unsigned bytesCount(obj o) { return rawCount(o) >> 1; }
static inline obj slotAt(obj o, unsigned i) { return ((obj *) o)[i + 2]; }
static inline void slotAtPut(obj o, unsigned i, obj v) { ((obj *) o)[i + 2] = v; }
static inline uint8_t *bvBytes(obj o) { return (uint8_t *) &(((obj *) o)[2]); }
static inline string bvString(obj o) { return string((char *) bvBytes(o), bytesCount(o)); }

static inline intptr_t rawCount(unsigned count, int isBs) {
  return (intptr_t) count << 1 | (isBs & 1);
}

static inline void _fillWith(obj o, unsigned n, obj v) {
  for (unsigned i = 0; i < n; i++) {
    slotAtPut(o, i, v);
  }
}

static int32_t nextInt(istream &f) {
  int32_t i;
  f.read((char *) &i, sizeof(int32_t));
  return ntohl(i);
}

struct VM;

typedef obj (*primitive_handler_t)(unsigned, VM &, obj);

static obj unhandledPrimitive(unsigned, VM &, obj);

struct VM {
  obj *allocBase;
  obj *allocPtr;
  obj *allocLimit;

  obj _nil;
  obj _true;
  obj _false;
  obj _Array;
  obj _Block;
  obj _Context;
  obj _Integer;

  primitive_handler_t primitiveTable[256];

  inline obj objClass(obj o) {
    return isSmi(o) ? _Integer : ((obj *) o)[1];
  }

  string className(obj c) {
    if (c == _nil) return "(nil)";
    return bvString(slotAt(c, 0));
  }

  string objClassName(obj o) {
    return className(objClass(o));
  }

  VM() {
    unsigned heapSize = 1048576000;
    allocBase = allocPtr = new obj[heapSize];
    allocLimit = &allocPtr[heapSize];
    for (int i = 0; i < 256; i++) primitiveTable[i] = unhandledPrimitive;
  }

  void gc() {
    cerr << "GC! urk" << endl;
    exit(1);
  }

  obj allocObj(unsigned nSlots, obj klass) {
  retry:
    obj result = (obj) allocPtr;
    *allocPtr++ = (obj) rawCount(nSlots, 0);
    *allocPtr++ = klass;
    allocPtr += nSlots;
    if (allocPtr >= allocLimit) {
      allocPtr = (obj *) result;
      gc();
      goto retry;
    }
    return result;
  }

  obj allocBytes(unsigned nBytes, obj klass) {
  retry:
    obj result = (obj) allocPtr;
    *allocPtr++ = (obj) rawCount(nBytes, 1);
    *allocPtr++ = klass;
    nBytes = (nBytes + sizeof(obj) - 1) & ~(sizeof(obj) - 1);
    allocPtr = (obj *) (((uint8_t *) allocPtr) + nBytes);
    if (allocPtr >= allocLimit) {
      allocPtr = (obj *) result;
      gc();
      goto retry;
    }
    return result;
  }

  obj allocBytes(string const &s, obj klass) {
    obj o = allocBytes(s.length(), klass);
    memcpy(bvBytes(o), s.c_str(), s.length());
    return o;
  }

  obj allocRawArray(unsigned n) {
    return allocObj(n, _Array);
  }

  obj allocArray(unsigned n, obj v) {
    obj o = allocRawArray(n);
    _fillWith(o, n, v);
    return o;
  }

  obj allocArray(unsigned n) {
    return allocArray(n, _nil);
  }

  void print(ostream &f, obj o) {
    if (isSmi(o)) {
      f << unSmi(o);
      return;
    }

    string className = objClassName(o);
    int isString = className.compare("String") == 0;

    if (!isString) {
      f << className;
    }

    if (isBytes(o)) {
      f << (isString ? '\"' : '{');
      ios_base::fmtflags saved(f.flags());
      char fill = f.fill('0');
      f << hex;

      for (int i = 0; i < bytesCount(o); i++) {
        char c = bvBytes(o)[i];
        if (isString) {
          switch (c) {
            case '\"': f << "\\\""; break;
            case '\n': f << "\\n"; break;
            default: f << c; break;
          }
        } else {
          f << setw(2) << ((unsigned) c & 0xff);
        }
      }

      f.flags(saved);
      f.fill(fill);

      f << (isString ? '\"' : '}');
    } else {
    }
  }

  int loadImage(string const &filename) {
    ifstream f(filename);
    vector<obj> table;

    if (!f.is_open()) return 0;
    if (nextInt(f) != 1 || !f) return 0; // wrong image version

    while (true) {
      int32_t len = nextInt(f);
      if (!f) break;
      int32_t code = nextInt(f);
      int32_t classIndex = nextInt(f);
      int32_t oopCount = nextInt(f);

      switch (code) {
        case 0:
          if (len != 5) return 0; // weird SmallInt object length
          if (oopCount != 0) return 0; // SmallInt doesn't get to have slots
          table.push_back(mkSmi(nextInt(f)));
          break;

        case 1: {
          int32_t byteCount = len - oopCount - 4;
          obj bv = allocBytes(byteCount, mkSmi(classIndex));
          f.read((char *) bvBytes(bv), byteCount);
          table.push_back(bv);
          break;
        }

        case 2: {
          obj o = allocObj(oopCount, mkSmi(classIndex));
          for (int i = 0; i < oopCount; i++) {
            slotAtPut(o, i, mkSmi(nextInt(f)));
          }
          table.push_back(o);
          break;
        }

        default:
          return 0; // unhandled code
      }
    }

    for (vector<obj>::iterator it = table.begin(); it != table.end(); ++it) {
      obj o = *it;
      if (!isSmi(o)) {
        unsigned count = 1 + (isBytes(o) ? 0 : slotCount(o));
        for (unsigned i = 0; i < count; i++) {
          (((obj *) o)[i + 1]) = table[unSmi(((obj *) o)[i + 1])];
        }
      }
    }

    _nil = table[0];
    _true = table[1];
    _false = table[2];
    _Array = table[3];
    _Block = table[4];
    _Context = table[5];
    _Integer = table[6];

    return 1;
  }

  obj searchClassMethodDictionary(obj c, obj selector) {
    obj methods = slotAt(c, 2);
    unsigned selectorLen = bytesCount(selector);
    char const *selectorBytes = (char const *) bvBytes(selector);
    for (unsigned i = 0; i < slotCount(methods); i++) {
      obj m = slotAt(methods, i);
      obj mname = slotAt(m, 0);
      // cerr << "  " << selectorBytes << " =?= " << bvString(mname) << endl;
      if ((bytesCount(mname) == selectorLen) &&
          (memcmp(bvBytes(mname), selectorBytes, selectorLen) == 0)) {
        return m;
      }
    }
    return 0;
  }

  obj buildContext(obj prevCtx, obj args, obj method) {
    unsigned tempCount = (unsigned) unSmi(slotAt(method, 4));
    unsigned maxStack = (unsigned) unSmi(slotAt(method, 3));
    obj ctx = allocObj(7, _Context);
    slotAtPut(ctx, 0, method);
    slotAtPut(ctx, 1, args);
    slotAtPut(ctx, 2, allocArray(tempCount));
    slotAtPut(ctx, 3, allocArray(maxStack));
    slotAtPut(ctx, 4, mkSmi(0));
    slotAtPut(ctx, 5, mkSmi(0));
    slotAtPut(ctx, 6, prevCtx);
    return ctx;
  }

  obj __ctx, __method, __args, __temps, __stack, __prevCtx, __receiver, __literals;
  unsigned __ip, __stackTop;
  uint8_t *__bytecode;

  void push(obj v) {
    slotAtPut(__stack, __stackTop++, v);
  }

  obj pop() {
    return slotAt(__stack, --__stackTop);
  }

  obj peek() {
    return slotAt(__stack, __stackTop - 1);
  }

  uint8_t nextByte() {
    return __bytecode[__ip++];
  }

  obj popArray(uint8_t count) {
    obj a = allocRawArray(count);
    __stackTop -= count;
    for (int i = 0; i < count; i++) {
      slotAtPut(a, i, slotAt(__stack, __stackTop + i));
    }
    return a;
  }

  void loadContext(obj ctx);

  void loadContextAndPush(obj ctx, obj v) {
    loadContext(ctx);
    push(v);
  }

  void storeRegisters() {
    slotAtPut(__ctx, 4, mkSmi(__ip));
    slotAtPut(__ctx, 5, mkSmi(__stackTop));
  }

  obj lookupMethod(obj c, obj selector) {
    while (c != _nil) {
      // cerr << "Searching in " << className(c) << " for "; print(cerr, selector); cerr << endl;
      obj m = searchClassMethodDictionary(c, selector);
      if (m != 0) return m;
      c = slotAt(c, 1);
    }
    return 0;
  }

  void sendMessage(obj c, obj newArgs, obj selector) {
    storeRegisters();
    // cerr << "Sending " << bvString(selector) << " via " << className(c) << " to ";
    // print(cerr, slotAt(newArgs, 0));
    // cerr << endl;
    obj method = lookupMethod(c, selector);
    if (method == 0) {
      cerr << "DNU ";
      print(cerr, slotAt(newArgs, 0));
      cerr << ' ';
      print(cerr, selector);
      cerr << endl;
      exit(2);
    }
    loadContext(buildContext(__ctx, newArgs, method));
  }

  void interpret() {
    while (__ctx != _nil) {
      uint8_t opcode, arg;
      opcode = nextByte();
      arg = opcode & 0xf;
      opcode = opcode >> 4;
      if (opcode == 0) {
        opcode = arg;
        arg = nextByte();
      }

      // cout << (int) opcode << ' ' << (int) arg;
      // for (int i = 0; i < __stackTop; i++) {
      //   cout << ' ';
      //   print(cout, slotAt(__stack, i));
      // }
      // cout << endl;
      // cout << flush;

      switch (opcode) {
        case 1: push(slotAt(__receiver, arg)); continue;
        case 2: push(slotAt(__args, arg)); continue;
        case 3: push(slotAt(__temps, arg)); continue;
        case 4: push(slotAt(__literals, arg)); continue;
        case 5:
          if (arg < 10) {
            push(mkSmi(arg));
          } else {
            switch (arg) {
              case 10: push(_nil); break;
              case 11: push(_true); break;
              case 12: push(_false); break;
            }
          }
          continue;
        case 6: slotAtPut(__receiver, arg, peek()); continue;
        case 7: slotAtPut(__temps, arg, peek()); continue;
        case 8: push(popArray(arg)); continue;
        case 9: {
          obj newArgs = pop();
          sendMessage(objClass(slotAt(newArgs, 0)), newArgs, slotAt(__literals, arg));
          continue;
        }
        case 10:
          switch (arg) {
            case 0: push((pop() == _nil) ? _true : _false); continue;
            case 1: push((pop() != _nil) ? _true : _false); continue;
          }
        case 11: {
          obj j = pop();
          obj i = pop();
          if (isSmi(i) && isSmi(j)) {
            switch (arg) {
              case 0: push((unSmi(i) < unSmi(j)) ? _true : _false); continue;
              case 1: push((unSmi(i) <= unSmi(j)) ? _true : _false); continue;
              case 2: push(mkSmi(unSmi(i) + unSmi(j))); continue;
            }
          } else {
            obj newArgs = allocRawArray(2);
            slotAtPut(newArgs, 0, i);
            slotAtPut(newArgs, 1, j);
            obj selector;
            switch (arg) {
              case 0: selector = allocBytes("<", _nil); break;
              case 1: selector = allocBytes("<=", _nil); break;
              case 2: selector = allocBytes("+", _nil); break;
            }
            sendMessage(objClass(i), newArgs, selector);
            continue;
          }
        }
        case 12: {
          uint8_t target = nextByte();
          obj block = allocObj(10, _Block);
          slotAtPut(block, 0, __method);
          slotAtPut(block, 1, __args);
          slotAtPut(block, 2, __temps);
          slotAtPut(block, 3, __stack);
          slotAtPut(block, 4, mkSmi(__ip));
          slotAtPut(block, 5, mkSmi(0));
          slotAtPut(block, 6, __prevCtx);
          slotAtPut(block, 7, mkSmi(arg));
          slotAtPut(block, 8, __ctx);
          slotAtPut(block, 9, mkSmi(__ip));
          push(block);
          __ip = target;
          continue;
        }
        case 13: {
          uint8_t primNumber = nextByte();
          // cerr << "  primNumber = " << (int) primNumber << endl;
          switch (primNumber) {
            case 6: {
              loadContext(pop());
              continue;
            }
            case 8: {
              obj block = pop();
              unsigned argloc = unSmi(slotAt(block, 7));
              unsigned argcount = arg - 1;
              for (unsigned i = 0; i < argcount; i++) {
                slotAtPut(slotAt(block, 2),
                          argloc + i,
                          slotAt(__stack, __stackTop - argcount + i));
              }
              __stackTop = __stackTop - argcount;
              storeRegisters();
              obj blockCtx = allocObj(10, _Block);
              slotAtPut(blockCtx, 0, slotAt(block, 0));
              slotAtPut(blockCtx, 1, slotAt(block, 1));
              slotAtPut(blockCtx, 2, slotAt(block, 2));
              slotAtPut(blockCtx, 3, allocArray(slotCount(slotAt(block, 3))));
              slotAtPut(blockCtx, 4, slotAt(block, 9));
              slotAtPut(blockCtx, 5, mkSmi(0));
              slotAtPut(blockCtx, 6, slotAt(__ctx, 6));
              slotAtPut(blockCtx, 7, slotAt(block, 7));
              slotAtPut(blockCtx, 8, slotAt(block, 8));
              slotAtPut(blockCtx, 9, slotAt(block, 9));
              loadContext(blockCtx);
              continue;
            }
            case 34: return;
            case 35: push(__ctx); continue;
            default:
              push(primitiveTable[primNumber](primNumber, *this, popArray(arg)));
              continue;
          }
        }
        case 14: push(slotAt(objClass(__receiver), arg + 5)); continue;
        case 15:
          switch (arg) {
            case 1: loadContextAndPush(__prevCtx, __receiver); continue;
            case 2: loadContextAndPush(__prevCtx, pop()); continue;
            case 3: loadContextAndPush(slotAt(slotAt(__ctx, 8), 6), pop()); continue;
            case 4: push(peek()); continue;
            case 5: pop(); continue;
            case 6: __ip = nextByte(); continue;
            case 7: {
              unsigned target = nextByte();
              if (pop() == _true) __ip = target;
              continue;
            }
            case 8: {
              unsigned target = nextByte();
              if (pop() == _false) __ip = target;
              continue;
            }
            case 11: {
              obj selector = slotAt(__literals, nextByte());
              obj newArgs = pop();
              obj definingClass = slotAt(__method, 5);
              obj super = slotAt(definingClass, 1);
              sendMessage(super, newArgs, selector);
              continue;
            }
            default:
              cerr << "Unhandled opcode 15 arg " << (int) arg << endl;
              exit(1);
          }
        default:
          cerr << "Unhandled opcode " << (int) opcode << ", " << (int) arg << endl;
          exit(1);
      }
    }
  }

  void installPrimitive(unsigned primNumber, primitive_handler_t handler) {
    primitiveTable[primNumber] = handler;
  }
};

void VM::loadContext(obj ctx) {
  __ctx = ctx;
  if (ctx != _nil) {
    __method = slotAt(ctx, 0);
    __args = slotAt(ctx, 1);
    __temps = slotAt(ctx, 2);
    __stack = slotAt(ctx, 3);
    __ip = unSmi(slotAt(ctx, 4));
    __stackTop = unSmi(slotAt(ctx, 5));
    __prevCtx = slotAt(ctx, 6);

    __receiver = slotAt(__args, 0);

    __bytecode = bvBytes(slotAt(__method, 1));
    __literals = slotAt(__method, 2);
  }
}

static obj unhandledPrimitive(unsigned primNumber, VM &vm, obj args) {
  cerr << "Primitive " << primNumber << " is unhandled" << endl;
  exit(1);
}

static obj prim_eq(unsigned primNumber, VM &vm, obj args) {
  return (slotAt(args, 0) == slotAt(args, 1)) ? vm._true : vm._false;
}

static obj prim_getClass(unsigned primNumber, VM &vm, obj args) {
  return vm.objClass(slotAt(args, 0));
}

static obj prim_objectSize(unsigned primNumber, VM &vm, obj args) {
  obj o = slotAt(args, 0);
  if (isSmi(o)) return mkSmi(0);
  if (isBytes(o)) return mkSmi(bytesCount(o));
  return mkSmi(slotCount(o));
}

static obj prim_slotAtPut(unsigned primNumber, VM &vm, obj args) {
  slotAtPut(slotAt(args, 1), unSmi(slotAt(args, 2)) - 1, slotAt(args, 0));
  return slotAt(args, 1);
}

static obj prim_mkObject(unsigned primNumber, VM &vm, obj args) {
  obj r = vm.allocObj(unSmi(slotAt(args, 1)), slotAt(args, 0));
  _fillWith(r, slotCount(r), vm._nil);
  return r;
}

static obj prim_numericQuotient(unsigned primNumber, VM &vm, obj args) {
  return mkSmi(unSmi(slotAt(args, 0)) / unSmi(slotAt(args, 1)));
}

static obj prim_numericModulo(unsigned primNumber, VM &vm, obj args) {
  return mkSmi(unSmi(slotAt(args, 0)) % unSmi(slotAt(args, 1)));
}

static obj prim_numericEq(unsigned primNumber, VM &vm, obj args) {
  return (slotAt(args, 0) == slotAt(args, 1)) ? vm._true : vm._false;
}

static obj prim_numericSub(unsigned primNumber, VM &vm, obj args) {
  return mkSmi(unSmi(slotAt(args, 0)) - unSmi(slotAt(args, 1)));
}

static obj prim_numericMul(unsigned primNumber, VM &vm, obj args) {
  // TODO overflow checks
  return mkSmi(unSmi(slotAt(args, 0)) * unSmi(slotAt(args, 1)));
}

static obj prim_mkBytes(unsigned primNumber, VM &vm, obj args) {
  return vm.allocBytes(unSmi(slotAt(args, 1)), slotAt(args, 0));
}

static obj prim_bytesRef(unsigned primNumber, VM &vm, obj args) {
  return mkSmi(bvBytes(slotAt(args, 0))[unSmi(slotAt(args, 1)) - 1]);
}

static obj prim_bytesSet(unsigned primNumber, VM &vm, obj args) {
  bvBytes(slotAt(args, 1))[unSmi(slotAt(args, 2)) - 1] = unSmi(slotAt(args, 0));
  return slotAt(args, 1);
}

static obj prim_bytesAppend(unsigned primNumber, VM &vm, obj args) {
  obj b = slotAt(args, 0);
  obj a = slotAt(args, 1);
  obj r = vm.allocBytes(bytesCount(a) + bytesCount(b), vm.objClass(a));
  memcpy(bvBytes(r), bvBytes(a), bytesCount(a));
  memcpy(bvBytes(r) + bytesCount(a), bvBytes(b), bytesCount(b));
  return r;
}

static obj prim_bytesCmp(unsigned primNumber, VM &vm, obj args) {
  obj a = slotAt(args, 0);
  obj b = slotAt(args, 1);
  int alen = bytesCount(a);
  int blen = bytesCount(b);
  int minlen = alen < blen ? alen : blen;
  int result = memcmp(bvBytes(a), bvBytes(b), minlen);
  if (result == 0) {
    result = alen - blen;
  }
  if (result < 0) return mkSmi(-1);
  if (result > 0) return mkSmi(1);
  return mkSmi(0);
}

static obj prim_slotAt(unsigned primNumber, VM &vm, obj args) {
  return slotAt(slotAt(args, 0), unSmi(slotAt(args, 1)) - 1);
}

static obj prim_extendObj(unsigned primNumber, VM &vm, obj args) {
  obj v = slotAt(args, 0);
  obj o = slotAt(args, 1);
  obj r = vm.allocObj(slotCount(o) + 1, vm.objClass(o));
  for (int i = 0; i < slotCount(o); i++) slotAtPut(r, i, slotAt(o, i));
  slotAtPut(r, slotCount(o), v);
  return r;
}

static obj prim_createWindow(unsigned primNumber, VM &vm, obj args) {
  cerr << "Creating window ";
  vm.print(cerr, slotAt(args, 0));
  cerr << endl;
  return vm.allocObj(0, slotAt(args, 0));
}

static obj prim_showWindow(unsigned primNumber, VM &vm, obj args) {
  cerr << "Show/hide window " << slotAt(args, 0) << " " << (slotAt(args, 1) == vm._true) << endl;
  return slotAt(args, 1);
}

static obj prim_setContentPane(unsigned primNumber, VM &vm, obj args) {
  cerr << "Setting content pane " << slotAt(args, 0) << endl;
  return slotAt(args, 0);
}

static obj prim_setWindowSize(unsigned primNumber, VM &vm, obj args) {
  cout << slotAt(args, 0) << "prim_setWindowSize:"
       << " height " << unSmi(slotAt(args, 1))
       << " width " << unSmi(slotAt(args, 2)) << endl;
  return slotAt(args, 0);
}

static obj prim_setWindowMenu(unsigned primNumber, VM &vm, obj args) {
  cout << slotAt(args, 0) << "prim_setWindowMenu" << endl;
  return slotAt(args, 0);
}

static obj prim_setWindowTitle(unsigned primNumber, VM &vm, obj args) {
  cout << slotAt(args, 0) << "prim_setWindowTitle: " << bvString(slotAt(args, 1)) << endl;
  return slotAt(args, 0);
}

static obj prim_repaintWindow(unsigned primNumber, VM &vm, obj args) {
  cout << slotAt(args, 0) << "prim_repaintWindow" << endl;
  return slotAt(args, 0);
}

static obj prim_createButton(unsigned primNumber, VM &vm, obj args) {
  cerr << "Creating button ";
  vm.print(cerr, slotAt(args, 0));
  cerr << " with label " << bvString(slotAt(args, 1)) << endl;
  return vm.allocObj(0, slotAt(args, 0));
}

static obj prim_createTextArea(unsigned primNumber, VM &vm, obj args) {
  cerr << "Creating textarea ";
  vm.print(cerr, slotAt(args, 0));
  cerr << endl;
  return vm.allocObj(0, slotAt(args, 0));
}

static obj prim_updateTextArea(unsigned primNumber, VM &vm, obj args) {
  cout << slotAt(args, 0) << "prim_updateTextArea: " << bvString(slotAt(args, 1)) << endl;
  return slotAt(args, 1);
}

static obj prim_createMenu(unsigned primNumber, VM &vm, obj args) {
  cerr << "Creating menu ";
  vm.print(cerr, slotAt(args, 0));
  cerr << " with title " << bvString(slotAt(args, 1)) << endl;
  return vm.allocObj(0, slotAt(args, 0));
}

static obj prim_createMenuItem(unsigned primNumber, VM &vm, obj args) {
  cerr << "Creating menu item ";
  vm.print(cerr, slotAt(args, 0));
  cerr << " with title " << bvString(slotAt(args, 1)) << endl;
  return slotAt(args, 0);
}

static obj prim_closeWindowHandler(unsigned primNumber, VM &vm, obj args) {
  cerr << "Ignoring prim_closeWindowHandler" << endl;
  return slotAt(args, 0);
}

static struct timespec ts_start;
static obj prim_getMilliseconds(unsigned primNumber, VM &vm, obj args) {
  struct timespec ts_end;
  clock_gettime(CLOCK_MONOTONIC, &ts_end);
  intptr_t delta = (ts_end.tv_sec - ts_start.tv_sec) * 1000;
  delta += (ts_end.tv_nsec / 1000000) - (ts_start.tv_nsec / 1000000);
  return mkSmi(delta);
}

int main(int argc, char *argv[]) {
  VM vm;

  clock_gettime(CLOCK_MONOTONIC, &ts_start);

  vm.installPrimitive(1, prim_eq);
  vm.installPrimitive(2, prim_getClass);
  vm.installPrimitive(4, prim_objectSize);
  vm.installPrimitive(5, prim_slotAtPut);
  vm.installPrimitive(7, prim_mkObject);
  vm.installPrimitive(11, prim_numericQuotient);
  vm.installPrimitive(12, prim_numericModulo);
  vm.installPrimitive(14, prim_numericEq);
  vm.installPrimitive(15, prim_numericMul);
  vm.installPrimitive(16, prim_numericSub);
  vm.installPrimitive(20, prim_mkBytes);
  vm.installPrimitive(21, prim_bytesRef);
  vm.installPrimitive(22, prim_bytesSet);
  vm.installPrimitive(24, prim_bytesAppend);
  vm.installPrimitive(26, prim_bytesCmp);
  vm.installPrimitive(30, prim_slotAt);
  vm.installPrimitive(31, prim_extendObj);
  vm.installPrimitive(60, prim_createWindow);
  vm.installPrimitive(61, prim_showWindow);
  vm.installPrimitive(62, prim_setContentPane);
  vm.installPrimitive(63, prim_setWindowSize);
  vm.installPrimitive(64, prim_setWindowMenu);
  vm.installPrimitive(65, prim_setWindowTitle);
  vm.installPrimitive(66, prim_repaintWindow);
  vm.installPrimitive(71, prim_createButton);
  vm.installPrimitive(73, prim_createTextArea);
  vm.installPrimitive(82, prim_updateTextArea);
  vm.installPrimitive(90, prim_createMenu);
  vm.installPrimitive(91, prim_createMenuItem);
  vm.installPrimitive(118, prim_closeWindowHandler);
  vm.installPrimitive(119, prim_getMilliseconds);

  if (!vm.loadImage("SmallWorld/src/image")) {
    cerr << "Could not load image" << endl;
    return 1;
  }

  cout << "Loaded!" << endl;

  {
    string bootCode = "[SmallWorld startUp. \
Transcript show: 0 tinyBenchmarks.] value";
    // "SmallWorld startUp";
    obj _String = vm.objClass(slotAt(vm.objClass(vm._true), 0)); // class String
    obj code = vm.allocBytes(bootCode, _String);
    obj selector = vm.allocBytes("doIt", _String);
    obj args = vm.allocRawArray(1);
    slotAtPut(args, 0, code);
    obj doIt = vm.searchClassMethodDictionary(vm.objClass(code), selector);
    obj ctx = vm.buildContext(vm._nil, args, doIt);
    vm.loadContext(ctx);
    vm.interpret();
    cout << "Boot complete" << endl;
    cout << "Used: " << vm.allocPtr - vm.allocBase << endl;
    cout << flush;
  }

  return 0;
}