一筋縄ではいかなかったので記事にしてみます。
Catalina を使い始めた方の助けになればと思います。
まず、何をしようとしていたかを書いておきます。
- Erlang 20.3.8.9 を macOS Catalina にインストールしたい。
- 古いですね。。
- asdf-erlang を使ってインストールしたい。
私がとった方法はまとめに書いてあります。
ソースから Erlang をコンパイルしてみる
問題の切り分けのために、まず ASDF を使わずに Erlang のコンパイルをしてみました。
このとき生じた問題を記録しておきます。
Stack check のデフォルト有効化
この問題は Erlang/OTP PR#2413 によって対処済です。
詳しくは分かりませんが、コンパイル時の stack check がデフォルトで有効化されたことにより、clang の潜在的なバグが顕在化してしまったようです。
したがって、CFLAGS
に -fno-stack-check
を含めればコンパイルエラーを回避できます。
Erlang 22.1.4 で反映されるようなので、それよりも古い Erlang を使いたい場合は、-fno-stack-check
を含めた CFLAGS
を自分で設定してコンパイルすれば大丈夫です。
デフォルトの CFLAGS
は -O2 -g
らしいので、私は下記のようにしました。
export CFLAGS="-O2 -g -fno-stack-check"
OpenSSL が見つからない
この問題は Catalina にする前からあったのかもしれないです。。
原因はここに書いてあります。
Configure するときに明示的に OpenSSL の場所を指定すれば大丈夫です。
私は下記のようにして Homebrew でインストールした OpenSSL を指定しました。
./configure --with-ssl=/usr/local/opt/openssl
Super Carrier がつくれない
この問題が最も大変でした。
症状としては、コンパイル時に下記のエラーが起きます。
=== Entering application hipe
VSN hipe.hrl
ERLC ../ebin/hipe_rtl.beam
erts_mmap: Failed to create super carrier of size 512 MB
make[3]: *** [../ebin/hipe_rtl.beam] Error 1
make[2]: *** [opt] Error 2
make[1]: *** [opt] Error 2
make: *** [secondary_bootstrap_build] Error 2
Super Carrier とは
Failed to create super carrier
とありますが、Super Carrier は何でしょうか?
ここに書いてありました。
パフォーマンス向上のために Erlang VM の起動時にある程度の大きさのメモリ領域を確保しておくらしいのですが、そのメモリ領域のことを Super Carrier を呼ぶそうです。
HiPE とは
Entering application hipe
とありますが、HiPE とは何でしょうか?
この記事が分かりやすかったです。
Erlang のソースコードをネイティブコードとしてコンパイルしてくれる機能のようです。
そして、コンパイラされたものは Super Carrier に配置されるようです。
32 bit のための配慮が仇となっていた
この問題の原因は、HiPE の生成物が所謂 low 2GB address space に配置されるように、プログラムがこのメモリ領域に Super Carrier をつくろうとしていることです。
よく知りませんが、32 bit システムではこのメモリ領域を使わざるを得ないみたいですね。。
Super Carrier と HiPE に関するコード(のひとつ)は Erlang/OTP リポジトリの erts/emulator/sys/common/erl_mmap.c
にあります。
ここで下記のような関数が定義されています(下記はリポジトリではなく、20.3.8.9 のソースから抜き出しました)。
static void *
os_mmap_virtual(char *ptr, UWord size, int exec)
{
int flags = ERTS_MMAP_VIRTUAL_FLAGS;
void* res;
#ifdef ERTS_ALC_A_EXEC
if (exec) {
ASSERT(!ptr);
/* OTP-19.0: Nice hack below cut-and-pasted from hipe_amd64.c */
# ifdef MAP_32BIT
/* If we got MAP_32BIT (Linux), then use that to ask for low memory */
flags |= MAP_32BIT;
# else
/* FreeBSD doesn't have MAP_32BIT, and it doesn't respect
a plain map_hint (returns high mappings even though the
hint refers to a free area), so we have to use both map_hint
and MAP_FIXED to get addresses below the 2GB boundary.
This is even worse than the Linux/ppc64 case.
Similarly, Solaris 10 doesn't have MAP_32BIT,
and it doesn't respect a plain map_hint. */
ptr = (char*)(512*1024*1024); /* 0.5GB */
# if defined(__FreeBSD__) || defined(__sun__)
flags |= MAP_FIXED;
# endif
# endif /* !MAP_32BIT */
}
#else /* !ERTS_ALC_A_EXEC */
ASSERT(!exec);
#endif
res = mmap((void *) ptr, (size_t) size, ERTS_MMAP_VIRTUAL_PROT,
flags, ERTS_MMAP_FD, 0);
if (res == (void *) MAP_FAILED)
return NULL;
return res;
}
macOS は MAP_32BIT
を持たないため、ptr = (char*)(512*1024*1024);
が実行されることになります。
FreeBSD doesn't have MAP_32BIT ...
で始まるコメントにあるように、below the 2GB boundary なメモリ領域を確保するために、関数 mmap()
に渡すメモリ領域の開始位置 ptr
を 0.5GB に固定しています。
Catalina で、プログラムが low 2GB address space にメモリが確保されるように指定することができなくなったのか、それとも low 2GB address space に限らずメモリ確保の指定ができなくなったのかは分かりませんが、とにかく、MAP_32BIT
を使った条件分岐部分を削除すれば、コンパイルが通るようになります。
ちなみに、ソースコードには os_mmap_virtual()
の第一引数 ptr
が NULL
の場合があり、したがって mmap()
に渡される ptr
が NULL
になる場合があるのですが、このとき確保されるメモリ領域の開始位置はシステムが決めてくれるみたいです。
なお、MAP_32BIT
を使った条件分岐部分の削除は Erlang/OTP の PR#1699 で反映されており、Erlang 21 以降であればこの問題は起こりません。
リリースノートには下記のように書いてあります。
OTP-14951 Application(s): erts Removed need for HiPE to allocate native executable memory in low 2GB address space on x86_64. Command line option +MXscs is thereby obsolete and ignored.
Catalina では 32 bit アプリが使えなくなるという話を聞きますが、この問題もそれに関連した話なのかもしれませんね。。
kerl を使って Erlang をコンパイルしてみる
asdf-erlang は内部的に kerl を使っています。
問題の切り分けのために、直接 kerl を使って Erlang をコンパイルしてみました。
ソースコードを変更することができないため、HiPE 自体を無効化するしかありませんでした。。
export KERL_CONFIGURE_OPTIONS="--disable-hipe --with-ssl=/usr/local/opt/openssl"
./kerl build 20.3.8
もしかしたらコンパイルオプションで問題を回避する方法もあるのかもしれませんが、調べきれませんでした。。
ASDF を使って Erlang をコンパイルしてみる
asdf-erlang を使う場合も KERL_CONFIGURE_OPTIONS
が効くはずです。
しかし、KERL_CONFIGURE_OPTIONS
の設定を二通り試してみたのですが、設定が反映されていないようでした。
-
export
する。 -
~/.kerlrc
に書く。
前者の原因はよく分かっていませんが、後者の理由は、asdf-erlang が実際に読みに行く設定ファイルが ~/.asdf/plugins/erlang/kerl-home/.kerlrc
であるためです。
したがって、下記のようにして KERL_CONFIGURE_OPTIONS
の設定を反映させることができました。
mkdir -p ~/.asdf/plugins/erlang/kerl-home
echo 'KERL_CONFIGURE_OPTIONS="--disable-hipe --with-ssl=/usr/local/opt/openssl"' > ~/.asdf/plugins/erlang/kerl-home/.kerlrc
ちなみに、この問題が kerl の Issue#320 と関係しているかな?と思ったのですが、まだ詳しく調べきれていません。
まとめ
まず、Catalina で ASDF を使って Erlang 20 をインストールしたい場合、HiPE を諦めざるを得ませんでした(少なくとも私の力量では)。
このことを理解した上で、私は下記の方法でインストールしました。
mkdir -p ~/.asdf/plugins/erlang/kerl-home
echo 'KERL_CONFIGURE_OPTIONS="--disable-hipe --with-ssl=/usr/local/opt/openssl"' > ~/.asdf/plugins/erlang/kerl-home/.kerlrc
export CFLAGS="-O2 -g -fno-stack-check"
asdf install erlang 20.3.8.9
# お掃除
unset CFLAGS
rm ~/.asdf/plugins/erlang/kerl-home/.kerlrc
インストールした Erlang は(使っているのは Elixir ですが)今のところ問題なく動作しています。
また、Mojave のときにインストールした Erlang も今のところ問題なく Catalina で動作しています。
ですが、可能であれば、Erlang 21 に移行したほうが良さそうです。。