概要
Crystal言語でのHelloWorldは以下のようなプログラムになる。
puts "Hello, World!"
crystal build --release greet.cr
これを、普通にリリースビルドすると、現在約192kbのバイナリが生成される。
先程知ったのだが、この記事にある方法でソースコードを書きビルドすると、同じ機能にもかかわらず約8.5kbのバイナリが出力される。
https://perens.com/2018/07/06/tiny-crystal-language-programs/
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" }
(インライン化した関数が残っているため、もう少し小さくなりそうだけど)ほぼ最小構成であることが確認できる。