Tiny Crystal Language Programs

Crystal, as currently deployed on Linux, creates rather large executables.  On my system, a zero-length source file results in an executable of 1087016 bytes in size, with a instruction size of 726121 bytes,  2856 bytes of initialized data, and 1263736 bytes of filled-with-zero-on-demand data (“bss”). This is a problem for embedded use. For example, if I could cross-compile to my ESP32 (ignoring that LLVM doesn’t have its instruction set implemented) I would already be using all of the available memory.

If I was to write an operating system kernel in Crystal, I’d want to have a specially-tweaked support library, as Linux has for common C APIs.

Fortunately, it is pretty easy to build Crystal programs without their support library. This will give you a program with no garbage collection, no exception handling, and no support libraries for Crystal’s built-in classes. It is now your job to fill these in to the extent that you want or need to.

Having stripped out the support library, a minimal Crystal program will compile to an executable only a few kilobytes in size.

Before you get too deep into this, be warned: objects won’t work the way you expect, and are probably pretty broken until they get some library support. The program as I show it below leaks memory, and may not allocate memory properly. There is no garbage collection and no exception handling. I may have written bugs. All of this is left as an exercise for the reader, this is Open Source at work 🙂

Here is a simple Hello World program in Crystal, creating and using a class, and printing a message. Compile this with the command:

crystal build minimal.cr --prelude="empty" -p --release --no-debug

minimal.cr

require "lib_c"
require "lib_c/i686-linux-gnu/c/stdlib"
require "lib_c/i686-linux-gnu/c/stdio"

def free(object)
  LibC.free(pointerof(object))
end

class String
  def to_unsafe
    pointerof(@c)
  end
end

class Foo
  def bar
    LibC.printf "Hello, World!\n"
  end
end

f = Foo.new
f.bar
free(f)

The executable emitted by compiling this is only 6304 bytes in size after the symbol table has been stripped. Instructions are 2179 bytes and initialized data 600 bytes, fill-with-zero-on-demand data is only 16 bytes. It loads with three shared libraries: linux-vdso.so.1, libc.so.6, ld-linux-x86-64.so.2 . These are of course much larger than the program. Doing without these libraries is left as another exercise for the reader.

Obviously the above program is meant to build on i686. Modify “i686-linux-gnu” to be your architecture, and if you are on a different OS, the libraries required above may be different.