LoginSignup
7
5

More than 5 years have passed since last update.

Crystal言語で小さなバイナリが作れるようになっていた

Posted at

概要

Crystal言語でのHelloWorldは以下のようなプログラムになる。

greet.cr
puts "Hello, World!"
crystal build --release greet.cr

これを、普通にリリースビルドすると、現在約192kbのバイナリが生成される。

先程知ったのだが、この記事にある方法でソースコードを書きビルドすると、同じ機能にもかかわらず約8.5kbのバイナリが出力される。
https://perens.com/2018/07/06/tiny-crystal-language-programs/

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)
crystal build minimal.cr --prelude="empty" -p --release --no-debug

解説

Crystalはコンパイル時に自動的に src/prelude.cr を読み込む。
このファイルの中で標準ライブラリを読み込んでいるため、CrystalではRubyのようにスラスラとコードがかけるようになっている。

一方、このファイル内で大量の標準ライブラリを読みこんでいるせいで、Crystalのバイナリは実際には使ってない機能もバイナリに含めてしまっている。
正確に言えば、コンパイラが使わないと分かっているコードは含まれないのだが、シグナルやスレッド等、使うかわからないコードはバイナリに含まれてしまう。

例にあるように、ビルドオプションに --prelude="empty" を加えると src/prelude.cr を読み込む代わりに src/empty.cr を読み込むようになる。このファイルではごくわずかなライブラリのみを読み込んでいるため、生成されるバイナリサイズが小さくなる。
ただしこのファイルは本当に最小なため、何かしたければ上のサンプルのようにいろいろ自作しなくてはいけない。

確認

Crystal生成結果はLLVM中間コードを通して確認できる。

$ crystal build minimal.cr --prelude="empty" --release --no-debug --emit llvm-ir

$ cat minimal.ll
; ModuleID = 'main_module'
source_filename = "main_module"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx"

@ARGV_UNSAFE = internal unnamed_addr global i8** null
@"'Hello, World!\0A'" = private constant { i32, i32, i32, [15 x i8] } { i32 1, i32 14, i32 14, [15 x i8] c"Hello, World!\0A\00" }

; Function Attrs: nounwind
define void @__crystal_main(i32 %argc, i8** %argv) local_unnamed_addr #0 {
alloca:
  store i8** %argv, i8*** @ARGV_UNSAFE, align 8
  %0 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ({ i32, i32, i32, [15 x i8] }, { i32, i32, i32, [15 x i8] }* @"'Hello, World!\0A'", i64 0, i32 3, i64 0)) #0
  ret void
}

; Function Attrs: nounwind
declare i32 @printf(i8* nocapture readonly, ...) local_unnamed_addr #0

; Function Attrs: nounwind uwtable
define i32 @main(i32 %argc, i8** %argv) local_unnamed_addr #1 {
entry:
  store i8** %argv, i8*** @ARGV_UNSAFE, align 8
  %0 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ({ i32, i32, i32, [15 x i8] }, { i32, i32, i32, [15 x i8] }* @"'Hello, World!\0A'", i64 0, i32 3, i64 0)) #0
  ret i32 0
}

; Function Attrs: nounwind
declare void @llvm.stackprotector(i8*, i8**) #0

attributes #0 = { nounwind }
attributes #1 = { nounwind uwtable "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" }

(インライン化した関数が残っているため、もう少し小さくなりそうだけど)ほぼ最小構成であることが確認できる。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5