# モジュールとBEAMファイルフォーマット

> Source: https://www.ymotongpoo.com/works/beam-book-ja/understanding_erts/beam_modules/


<a id="CH-beam_modules"></a>

<a id="modules"></a>

## モジュール

Erlangにおけるモジュールとは、Erlangの関数を格納するファイルです。
関連する関数をまとめ、他のモジュールから利用するための単位を提供します。
Erlangのコードロードとは、コンパイル済みのErlangモジュールをBEAM仮想マシンに読み込む処理を指します。
この読み込みは、起動時に静的に行うことも、システム稼働中に動的に行うこともできます。

Erlangはホットコードローディングに対応しています。
システムを停止・再起動することなく、稼働中のままモジュールを更新できる機能です。
開発やデバッグの最中にはとても重宝します。
運用の仕方によっては、24時間365日稼働し続けるシステムを止めずにモジュールをアップグレードできるため、保守の場面でも役立ちます。

新しいコードを読み込んでも、それを実行中のプロセスが残っている間は旧バージョンがメモリ上に残り続けます。
そうしたプロセスがなくなった時点で、旧コードはシステムからパージされます。
ここで注意が必要なのは、最初のバージョンがパージされる前に3番目のバージョンを読み込んだ場合の挙動です。
デフォルトの動作では、最初のバージョンを参照している（スタック上に呼び出しを持つ）プロセスはすべて強制終了されます。

`code:load_file(Module)` 関数を使うと、モジュールを動的にシステムへ読み込めます。
新しいモジュールを読み込んだ後は、完全修飾呼び出し（`Module:function` の形の呼び出し。リモートコールとも呼ばれます）がすべて新バージョンへ向かうようになります。
ただし、リモートコールを含まないサーバーループは、そのまま旧コードを実行し続ける点に注意してください。

コードサーバーは、読み込み済みモジュールとそのコードの管理を担うBEAM仮想マシンの一部です。

Erlangの分散モデルとホットコードローディング機能を組み合わせると、分散システム内の複数ノードにまたがるコード更新も可能になります。
とはいえ、これは慎重な調整を要する複雑な作業です。

<a id="BEAM_files"></a>

## BEAMファイルフォーマット

BEAMファイルフォーマットに関する最も確実な情報源は、当然ながら `beam_lib.erl` のソースコードです（[GitHub上のbeam_lib.erl](https://github.com/erlang/otp/blob/maint/lib/stdlib/src/beam_lib.erl)を参照）。
また、Beamの主要開発者兼メンテナが書いた、読みやすいものの少し古くなった解説文書も存在します（[erlang.seの記事](http://www.erlang.se/~bjorn/beam_file_format.html)を参照）。

BEAMファイルフォーマットは、Interchange File Format（EA IFF）をもとに、2箇所だけ小さな変更を加えたものです。
その変更点についてはこの後すぐに触れます。
IFFファイルは、ヘッダーに続けて複数の「チャンク」が並ぶ構造を取ります。
IFF仕様には主に画像や音楽を扱う標準チャンク型がいくつも定義されていますが、独自の名前を持つチャンクを指定することも許されており、BEAMが使っているのはこちらの仕組みです。

{{< message >}}
BEAMファイルは標準のIFFファイルと異なり、各チャンクが2バイト境界ではなく4バイト境界（32ビットワード）に揃えられます。
この標準IFFファイルではないことを示すため、IFFヘッダーには「FOR」ではなく「FOR1」というタグが付けられます。
これはIFF仕様が将来の拡張のために提案しているタグです。
{{< /message >}}

BEAMはフォームタイプとして「BEAM」を使います。BEAMファイルのヘッダーは次のようなレイアウトを取ります。

``` erlang
BEAMHeader = <<
  IffHeader:4/unit:8 = "FOR1",
  Size:32/big,                  // ビッグエンディアン。残りのバイト数
  FormType:4/unit:8 = "BEAM"
>>
```

ヘッダーに続けて複数のチャンクが並びます。各チャンクのサイズは4の倍数に揃えられ、チャンクごとに固有のヘッダー（以下）を持ちます。

{{< message >}}
この境界揃えは、一部のプラットフォームで重要な意味を持ちます。
境界に揃っていないメモリへのバイトアクセスがハードウェア例外（Linuxでは`SIGBUS`という名前）を引き起こすことがあるためです。
性能低下で済む場合もあれば、例外によってVMがクラッシュすることもあります。
{{< /message >}}

``` erlang
BEAMChunk = <<
  ChunkName:4/unit:8,           // "Code", "Atom", "StrT", "LitT", ...
  ChunkSize:32/big,
  ChunkData:ChunkSize/unit:8,   // データフォーマットはChunkNameによって決まる
  Padding4:0..3/unit:8
>>
```

このファイルフォーマットでは、あらゆる領域の先頭にその領域のサイズを置くため、ディスクから読みながらそのままファイルを解析できます。
BEAMファイルの構造と内容を具体的に見るために、ファイルからすべてのチャンクを取り出す小さなプログラムを書いてみましょう。
プログラムをできるだけ単純で読みやすく保つため、読み込みながら解析するのではなく、ファイル全体をバイナリとしてメモリに読み込んでから各チャンクを解析する方式にします。
最初の一歩は、すべてのチャンクの一覧を取得することです。

``` erlang
-module(beamfile1).
-export([read/1]).

read(Filename) ->
   {ok, File} = file:read_file(Filename),
   <<"FOR1",
     Size:32/integer,
     "BEAM",
     Chunks/binary>> = File,
   {Size, read_chunks(Chunks, [])}.

read_chunks(<<N,A,M,E, Size:32/integer, Tail/binary>>, Acc) ->
   %% 各チャンクを4バイトの倍数に揃える
   ChunkLength = align_by_four(Size),
   <<Chunk:ChunkLength/binary, Rest/binary>> = Tail,
   read_chunks(Rest, [{[N,A,M,E], Size, Chunk}|Acc]);
read_chunks(<<>>, Acc) -> lists:reverse(Acc).

align_by_four(N) -> (4 * ((N+3) div 4)).
```

実行例は次のようになります。

``` erlang
> beamfile1:read("beamfile1.beam").
{1116, [{"AtU8",127,
 <<0,0,0,16,9,98,101,97,109,102,105,108,101,49,4,114,101,
 97,100,4,102,105,108,...>>}, {"Code",362,
 <<0,0,0,16,0,0,0,0,0,0,0,178,0,0,0,16,0,0,0,5,1,16,...>>},
 {"StrT",8,<<"FOR1BEAM">>},
 {"ImpT",88,<<0,0,0,7,0,0,0,3,0,0,0,4,0,0,0,1,0,0,0,8,...>>},
 {"ExpT",40,<<0,0,0,3,0,0,0,15,0,0,0,1,0,0,0,15,0,0,0,...>>}, {"Meta",29,
 <<131,108,0,0,0,1,104,2,100,0,16,101,110,97,98,108,101, 100,...>>},
 {"LocT",28,<<0,0,0,2,0,0,0,10,0,0,0,1,0,0,0,11,0,...>>}, {"Attr",40,
 <<131,108,0,0,0,1,104,2,100,0,3,118,115,110,108,0,...>>}, {"CInf",122,
 <<131,108,0,0,0,3,104,2,100,0,7,118,101,114,115,...>>}, {"Dbgi",49,
 <<131,104,3,100,0,13,100,101,98,117,103,95,105,110,...>>},
 {"Line",31,<<0,0,0,0,0,0,0,0,0,0,0,12,0,...>>},
 {"Type",80,<<0,0,0,1,0,0,0,4,31,255,0,0,...>>}]}
```

ここでBEAMが使用するチャンク名を確認できます。

<a id="atom_table_chunk"></a>

### アトムテーブルチャンク

`Atom` という名前のチャンクか `AtU8` という名前のチャンクのいずれかが必須です。
このチャンクには、モジュールが参照するすべてのアトムが格納されます。
`latin1` エンコーディングのソースファイルでは `Atom` という名前のチャンクが使われ、`utf8` エンコーディングのモジュールでは `AtU8` という名前のチャンクが使われます。
アトムチャンクのフォーマットは次のとおりです。

``` erlang
AtomChunk = <<
  ChunkName:4/unit:8 = "Atom",
  ChunkSize:32/big,
  NumberOfAtoms:32/big,
  [<<AtomLength:8, AtomName:AtomLength/unit:8>> || repeat NumberOfAtoms],
  Padding4:0..3/unit:8
>>
```

`AtU8` チャンクのフォーマットは、チャンク名が `AtU8` である点を除けば上記と同じです。

{{< message >}}
モジュール名は常にテーブルの最初のアトム（アトムインデックス0）として格納されます。
{{< /message >}}

それでは、BEAMファイルリーダーにアトムチャンクのデコード処理を追加してみましょう。

``` erlang
-module(beamfile2).
-export([read/1]).

read(Filename) ->
   {ok, File} = file:read_file(Filename),
   <<"FOR1",
     Size:32/integer,
     "BEAM",
     Chunks/binary>> = File,
   {Size, parse_chunks(read_chunks(Chunks, []),[])}.

read_chunks(<<N,A,M,E, Size:32/integer, Tail/binary>>, Acc) ->
   %% 各チャンクを4バイトの倍数に揃える
   ChunkLength = align_by_four(Size),
   <<Chunk:ChunkLength/binary, Rest/binary>> = Tail,
   read_chunks(Rest, [{[N,A,M,E], Size, Chunk}|Acc]);
read_chunks(<<>>, Acc) -> lists:reverse(Acc).

parse_chunks([{"AtU8", _Size,
             <<_Numberofatoms:32/integer, Atoms/binary>>}
            | Rest], Acc) ->
   parse_chunks(Rest,[{atoms,parse_atoms(Atoms)}|Acc]);
parse_chunks([Chunk|Rest], Acc) -> %% まだ実装していないチャンク
   parse_chunks(Rest, [Chunk|Acc]);
parse_chunks([],Acc) -> Acc.

parse_atoms(<<Atomlength, Atom:Atomlength/binary, Rest/binary>>) when Atomlength > 0->
   [list_to_atom(binary_to_list(Atom)) | parse_atoms(Rest)];
parse_atoms(_Alignment) -> [].

align_by_four(N) -> (4 * ((N+3) div 4)).
```

<a id="export_table_chunk"></a>

### エクスポートテーブルチャンク

`ExpT`（EXPort Tableの略）という名前のチャンクは必須で、どの関数がエクスポートされているかという情報を保持します。

エクスポートチャンクのフォーマットは次のとおりです。

``` erlang
ExportChunk = <<
  ChunkName:4/unit:8 = "ExpT",
  ChunkSize:32/big,
  ExportCount:32/big,
  [ << FunctionName:32/big,
       Arity:32/big,
       Label:32/big
    >> || repeat ExportCount ],
  Padding4:0..3/unit:8
>>
```

`FunctionName` はアトムテーブル内のインデックスです。

アトムを扱う節の後に次の節を追加することで、`parse_chunk` 関数を拡張できます。

``` erlang
parse_chunks([{"ExpT", _Size,
             <<_Numberofentries:32/integer, Exports/binary>>}
            | Rest], Acc) ->
   parse_chunks(Rest,[{exports,parse_exports(Exports)}|Acc]);

...

parse_exports(<<Function:32/integer,
               Arity:32/integer,
               Label:32/integer,
               Rest/binary>>) ->
   [{Function, Arity, Label} | parse_exports(Rest)];
parse_exports(<<>>) -> [].
```

<a id="import_table_chunk"></a>

### インポートテーブルチャンク

`ImpT`（IMPort Tableの略）という名前のチャンクは必須で、どの関数がインポートされているかという情報を保持します。

このチャンクのフォーマットは次のとおりです。

``` erlang
ImportChunk = <<
  ChunkName:4/unit:8 = "ImpT",
  ChunkSize:32/big,
  ImportCount:32/big,
  [ << ModuleName:32/big,
       FunctionName:32/big,
       Arity:32/big
    >> || repeat ImportCount ],
  Padding4:0..3/unit:8
>>
```

ここで `ModuleName` と `FunctionName` はアトムテーブル内のインデックスです。

{{< message >}}
インポートテーブルを解析するコードは、エクスポートテーブルを解析するコードとよく似ていますが、完全に同じではありません。
どちらも32ビット整数3つ組ですが、その意味が異なります。完全なコードは章末を参照してください。
{{< /message >}}

<a id="code_chunk"></a>

### コードチャンク

`Code` という名前のチャンクはモジュールのBEAMコードを保持し、必須です。このチャンクのフォーマットは次のとおりです。

``` erlang
CodeChunk = <<
  ChunkName:4/unit:8 = "Code",
  ChunkSize:32/big,
  SubSize:32/big,
  InstructionSet:32/big,        % エミュレータ内のコードバージョンと一致していなければならない
  OpcodeMax:32/big,
  LabelCount:32/big,
  FunctionCount:32/big,
  Code:(ChunkSize-SubSize)/binary,  % 残り全部のデータ
  Padding4:0..3/unit:8
>>
```

`SubSize` フィールドには、コードが始まるまでのワード数が格納されます。これにより、古いローダーを壊すことなくコードチャンクへ新しい情報フィールドを追加できます。

`InstructionSet` フィールドは、そのファイルが使用する命令セットのバージョンを示します。命令に非互換な変更が加えられるたびに、このバージョン番号は増加します。

`OpcodeMax` フィールドは、コード内で使用されているオペコードのうち最大の番号を示します。新しい命令をシステムに追加する場合でも、ファイル内で使われている命令がローダーの知っている範囲に収まっていれば、古いローダーでも新しいファイルを読み込めます。

`LabelCount` フィールドにはラベルの数が格納されており、ローダーは一度の呼び出しで適切なサイズのラベルテーブルを事前確保できます。`FunctionCount` フィールドには関数の数が格納されており、同様に関数テーブルも効率よく事前確保できます。

`Code` フィールドには命令が連なって格納されており、各命令は次のフォーマットを持ちます。

``` erlang
Instruction = <<
  InstructionCode:8,
  [beam_asm:encode(Argument) || repeat Arity]
>>
```

ここで `Arity` はテーブルにハードコードされています。このテーブルは、エミュレータをソースからビルドする際に `genop` スクリプトが `ops.tab` から生成します。

`beam_asm:encode` が生成する符号化については、後述の[コンパクトターム符号化](#SEC-BeamModulesCTE)の節で説明します。

次のコードをプログラムに追加すれば、コードチャンクを解析できます。

``` erlang
parse_chunks([{"Code", Size, <<SubSize:32/integer,Chunk/binary>>
              } | Rest], Acc) ->
   <<Info:SubSize/binary, Code/binary>> = Chunk,
   %% 8はChunkSizeとSubSizeを合わせたサイズ
   OpcodeSize = Size - SubSize - 8,
   <<OpCodes:OpcodeSize/binary, _Align/binary>> = Code,
   parse_chunks(Rest,[{code,parse_code_info(Info), OpCodes}
                      | Acc]);

...

parse_code_info(<<Instructionset:32/integer,
          OpcodeMax:32/integer,
          NumberOfLabels:32/integer,
          NumberOfFunctions:32/integer,
          Rest/binary>>) ->
   [{instructionset, Instructionset},
    {opcodemax, OpcodeMax},
    {numberoflabels, NumberOfLabels},
    {numberofFunctions, NumberOfFunctions} |
    case Rest of
     <<>> -> [];
     _ -> [{newinfo, Rest}]
    end].
```

BEAM命令のデコード方法は、まさに「BEAM Instructions」という名の章で学びます。

<a id="_string_table_chunk"></a>

### 文字列テーブルチャンク

`StrT` という名前のチャンクは必須で、モジュール内のすべての定数文字列リテラルを1つの長い文字列として保持します。文字列リテラルが1つもない場合でも、チャンク自体は存在し、中身は空でサイズ0になります。

このチャンクのフォーマットは次のとおりです。

``` erlang
StringChunk = <<
  ChunkName:4/unit:8 = "StrT",
  ChunkSize:32/big,
  Data:ChunkSize/binary,
  Padding4:0..3/unit:8
>>
```

文字列チャンクは、バイト列をそのままバイナリに変換するだけで簡単に解析できます。

``` erlang
parse_chunks([{"StrT", _Size, <<Strings/binary>>} | Rest], Acc) ->
    parse_chunks(Rest,[{strings,binary_to_list(Strings)}|Acc]);
```

<a id="_attributes_chunk"></a>

### 属性チャンク

`Attr` という名前のチャンクは省略可能ですが、一部のOTPツールは属性が存在することを前提にしています。たとえばリリースハンドラは `"vsn"` 属性の存在を前提にしています。ファイルからバージョン属性を取得するには `beam_lib:version(Filename)` を使います。この関数は、`"vsn"` 属性を持つ属性チャンクが存在することを前提としています。

このチャンクのフォーマットは次のとおりです。

``` erlang
AttributesChunk = <<
  ChunkName:4/unit:8 = "Attr",
  ChunkSize:32/big,
  Attributes:ChunkSize/binary,
  Padding4:0..3/unit:8
>>
```

属性チャンクは次のように解析できます。

``` erlang
parse_chunks([{"Attr", Size, Chunk} | Rest], Acc) ->
    <<Bin:Size/binary, _Pad/binary>> = Chunk,
    Attribs = binary_to_term(Bin),
    parse_chunks(Rest,[{attributes,Attribs}|Acc]);
```

<a id="_compilation_information_chunk"></a>

### コンパイル情報チャンク

`CInf` という名前のチャンクは省略可能ですが、一部のOTPツールはこの情報が存在することを前提にしています。

フォーマットは次のとおりです。

``` erlang
CompilationInfoChunk = <<
  ChunkName:4/unit:8 = "CInf",
  ChunkSize:32/big,
  Data:ChunkSize/binary,
  Padding4:0..3/unit:8
>>
```

コンパイル情報チャンクは次のように解析できます。

``` erlang
parse_chunks([{"CInf", Size, Chunk} | Rest], Acc) ->
    <<Bin:Size/binary, _Pad/binary>> = Chunk,
    CInfo = binary_to_term(Bin),
    parse_chunks(Rest,[{compile_info,CInfo}|Acc]);
```

<a id="_local_function_table_chunk"></a>

### ローカル関数テーブルチャンク

`LocT` という名前のチャンクは省略可能で、相互参照ツール向けに用意されています。

フォーマットはエクスポートテーブルと同じです。

``` erlang
LocalFunTableChunk = <<
  ChunkName:4/unit:8 = "LocT",
  ChunkSize:32/big,
  FunctionCount:32/big,
  [ << FunctionName:32/big,
       Arity:32/big,
       Label:32/big
    >> || repeat FunctionCount ],
  Padding4:0..3/unit:8
>>
```

{{< message >}}
ローカル関数テーブルを解析するコードは、エクスポートテーブルやインポートテーブルを解析するコードと基本的に同じであり、実際にはすべてのテーブルのエントリを同じ関数で解析できます。完全なコードは章末を参照してください。
{{< /message >}}

<a id="_literal_table_chunk"></a>

### リテラルテーブルチャンク

`LitT` という名前のチャンクは省略可能で、モジュールのソースにあるリテラル値のうち即値ではないものを、すべて圧縮した形で保持します。このチャンクのフォーマットは次のとおりです。

``` erlang
LiteralTableChunk = <<
  ChunkName:4/unit:8 = "LitT",
  ChunkSize:32/big,
  UncompressedSize:32/big,      % メモリを確保する際にサイズがわかると都合がよい
  CompressedLiterals:ChunkSize/binary,
  Padding4:0..3/unit:8
>>
```

`CompressedLiterals` は、展開後にちょうど `UncompressedSize` バイトになっている必要があります。テーブル内の各リテラルは、外部タームフォーマット（`erlang:term_to_binary`）で符号化されています。`CompressedLiterals` のフォーマットは次のとおりです。

``` erlang
CompressedLiterals = <<
  Count:32/big,
  [ <<Size:32/big, Literal:binary>>  || repeat Count ]
>>
```

テーブル全体は `zlib:compress/1`（deflateアルゴリズム）で圧縮されており、`zlib:uncompress/1`（inflateアルゴリズム）で展開できます。

このチャンクは次のように解析できます。

``` erlang
parse_chunks([{"LitT", _ChunkSize,
              <<_UnCompressedTableSize:32, Compressed/binary>>}
             | Rest], Acc) ->
    <<_NumLiterals:32,Table/binary>> = zlib:uncompress(Compressed),
    Literals = parse_literals(Table),
    parse_chunks(Rest,[{literals,Literals}|Acc]);

...

parse_literals(<<Size:32,Literal:Size/binary,Tail/binary>>) ->
    [binary_to_term(Literal) | parse_literals(Tail)];
parse_literals(<<>>) -> [].
```

<a id="_abstract_code_chunk"></a>

### 抽象コードチャンク

`Abst` という名前のチャンクは省略可能で、抽象構文の形をしたコードを保持することがあります。コンパイラに `debug_info` フラグを渡すと、モジュールの抽象構文木がこのチャンクに格納されます。デバッガやXrefのようなOTPツールは、この抽象構文形式を必要とします。このチャンクのフォーマットは次のとおりです。

``` erlang
AbstractCodeChunk = <<
  ChunkName:4/unit:8 = "Abst",
  ChunkSize:32/big,
  AbstractCode:ChunkSize/binary,
  Padding4:0..3/unit:8
>>
```

このチャンクは次のように解析できます。

``` erlang
parse_chunks([{"Abst", _ChunkSize, <<>>} | Rest], Acc) ->
    parse_chunks(Rest,Acc);
parse_chunks([{"Abst", _ChunkSize, <<AbstractCode/binary>>} | Rest], Acc) ->
    parse_chunks(Rest,[{abstract_code,binary_to_term(AbstractCode)}|Acc]);
```

<a id="_additional_beam_chunks"></a>

### 追加のBEAMチャンク

Erlang OTP 16以降、いくつかの新しいチャンクが追加されています。

- **タイプチャンク（`Type`）**：OTP 25で導入され、JIT最適化を助けるための簡略化された型情報を保持する。
- **関数テーブルチャンク（`FunT`）**：無名関数（fun）のメタデータ。
- **メタデータチャンク（`Meta`）**：ドキュメントや注釈といった省略可能なメタデータを保持する。

これら新しいチャンクのフォーマットは次のとおりです。

**タイプチャンク（`Type`）**

``` erlang
TypeChunk = <<
  ChunkName:4/unit:8 = "Type",
  ChunkSize:32/big,
  Version:32/big,
  Count:32/big,
  TypeData:(Count * 16)/binary,
  Padding4:0..3/unit:8
>>
```

タイプチャンクは、JITコンパイラが最適化に使う簡略化された型情報を保持します。各型エントリは固定16バイトのレコードです。このデータはErlangの外部タームフォーマットではないため、`binary_to_term` では復号できません。符号化の詳細は、コンパイラ内の `beam_types.erl` を参照してください。

解析コードは次のとおりです。

``` erlang
parse_chunks([{"Type", _Size, Chunk} | Rest], Acc) ->
    <<_Version:32/big, Count:32/big, TypeData/binary>> = Chunk,
    Types = parse_types(Count, TypeData, []),
    parse_chunks(Rest, [{type_info, Types}|Acc]);
```

- **UTF-8アトムチャンク（`AtU8`）**：Atomチャンクと同じフォーマットで、UTF-8エンコーディングが必要な場合に使う。

``` erlang
AtU8Chunk = <<
  ChunkName:4/unit:8 = "AtU8",
  ChunkSize:32/big,
  NumberOfAtoms:32/big,
  [<<AtomLength:8, AtomName:AtomLength/unit:8>> || repeat NumberOfAtoms],
  Padding4:0..3/unit:8
>>
```

- **関数テーブル（`FunT`）**：ローカルな無名関数に関する情報。

``` erlang
FunTableChunk = <<
  ChunkName:4/unit:8 = "FunT",
  ChunkSize:32/big, FunCount:32/big, [<<FunctionName:32/big, Arity:32/big,
      Label:32/big
   >> || repeat FunCount],
  Padding4:0..3/unit:8
>>
```

- **メタデータチャンク（`Meta`）**：注釈やユーザー定義のプロパティといったメタデータを格納する。

``` erlang
MetaChunk = <<
  ChunkName:4/unit:8 = "Meta",
  ChunkSize:32/big,
  MetaInfo:ChunkSize/binary,
  Padding4:0..3/unit:8
>>
```

解析例は次のとおりです。

``` erlang
parse_chunks([{"Meta", Size, Chunk} | Rest], Acc) ->
    <<MetaInfo:Size/binary, _Pad/binary>> = Chunk,
    Meta = binary_to_term(MetaInfo),
    parse_chunks(Rest, [{meta, Meta}|Acc]);
```

<a id="_encryption"></a>

### 暗号化

Erlangでは、BEAMファイル内のデバッグ情報を暗号化できます。この機能により、開発者はソースコードを非公開にしたまま、デバッガやXrefといったツールを利用し続けられます。

暗号化されたデバッグ情報を利用するには、コンパイラと `beam_lib` の両方に鍵を渡す必要があります。この鍵は文字列として指定し、大文字・小文字・数字・記号を含む32文字以上にすることが望ましいとされています。

現時点で唯一かつデフォルトの暗号アルゴリズムは `des3_cbc` で、Cipher Block Chainingモードのtriple DES（Data Encryption Standard）を意味します。鍵文字列は `erlang:md5/1` によって撹拌され、`des3_cbc` で使う鍵が生成されます。

鍵の指定方法は2通りあります。

1. コンパイラオプション：コンパイラオプション `{debug_info_key,Key}` と、`beam_lib` がデバッグ情報を復号する際に鍵を返す関数を登録するための `crypto_key_fun/1` を使う。
2. `.erlang.crypt` ファイル：関数が登録されていない場合、`beam_lib` はカレントディレクトリ、次にユーザーのホームディレクトリ、最後に `filename:basedir(user_config, "erlang")` の順に `.erlang.crypt` ファイルを探す。ファイルが見つかり鍵が含まれていれば、`beam_lib` は暗黙のうちに鍵を返す関数を作成して登録する。

`.erlang.crypt` ファイルには、`{debug_info, Mode, Module, Key}` という形のタプルのリストを記述します。`Mode` は暗号アルゴリズムの種類（現在は `des3_cbc` のみ許可）、`Module` はアトム（その場合 `Key` はそのモジュールにのみ使われる）か `[]`（その場合 `Key` はすべてのモジュールに使われる）、`Key` は空でない鍵文字列です。

`Mode` と `Module` の両方が一致する最初のタプルの鍵が使われます。暗号化されたデバッグ情報の安全性を保つには、一意な鍵を使い、それを安全に管理することが重要です。

<a id="_compression"></a>

### 圧縮

Erlangコンパイラに `compressed` フラグを渡すと、コンパイラは生成するBEAMファイルを圧縮するようになります。これによりファイルサイズが大幅に小さくなることがあり、ディスク容量が貴重な環境で有用です。

`compressed` フラグは、BEAMファイル内のErlangコードとリテラルデータを含む部分にzlib圧縮を適用します。コードの実行速度には影響しません。展開処理はコードの実行時ではなく、メモリへの読み込み時に行われるためです。

`compressed` フラグを使うには、次のように `compile` 関数のオプションとして渡します。

``` erlang
compile:file(Module, [compressed]).
```

コマンドラインコンパイラ `erlc` を使う場合は、`+compressed` オプションを渡せます。

``` bash
erlc +compressed module.erl
```

`compressed` フラグはBEAMファイルのサイズを縮小できる一方で、コードの展開が必要になる分、モジュールの読み込みにかかる時間は増加します。つまりディスク容量と読み込み時間のトレードオフです。

<a id="SEC-BeamModulesCTE"></a>

### コンパクトターム符号化

`beam_asm:encode` が使う符号化方式を見ていきます。BEAMファイルは、単純なタームを効率よく格納するためのコンパクトな符号化方式を採用しています。これはファイルサイズの削減に特化した方式で、タームのメモリ上のレイアウトとは大きく異なります。復号後、符号化された値はすべて、フルサイズのマシンワードまたはErlangタームへと展開されます。

{{< message >}}
`beam_asm` は、Erlangディストリビューションに含まれる `compiler` アプリケーション内のモジュールで、BEAMモジュールのバイナリ内容を組み立てるために使われます。
{{< /message >}}

この符号化方式の主な目的はコンパクトさです。単純なタームをより少ないビット数に詰め込むことで効率よく符号化します。復号すると、これらの圧縮されたタームは標準的なマシンワードまたはErlangタームへと展開されます。

タグフォーマットは次のとおりです。ここには最近の変更（特にOTP 20とOTP 25のもの）も反映されています。

![タグフォーマットの一覧。上位5ビットが使われない場合は空欄、下位3ビットがタグを表す。拡張タグ(111)ではさらに上位5ビットが種別を示す](tag-format-overview.png)
*図1: コンパクトターム符号化のタグフォーマット一覧*

{{< message >}}
OTP 20以降、このタグフォーマットは変更され、`Extended - Float` はなくなりました。それ以降のタグ値はすべて1つずつ繰り下がっています。リストは`2#10111`、fpregは`2#100111`、アロケーションリストは`2#110111`、リテラルは`2#1010111`です。浮動小数点値は、現在ではBEAMファイルのリテラル領域に直接格納されるようになりました。
{{< /message >}}

先頭バイトの最初の3ビットには、後続の値の型を示すタグが格納されます。この3ビットがすべて1（特殊な値7、`beam_opcodes.hrl` の `?tag_z`）の場合は、さらに数ビットが使われます。

16未満の値は、ビット3を0にした上で、ビット4〜7にそのまま格納されます。

![16未満の値のビットレイアウト。上位4ビットに値、ビット3は0、下位3ビットにタグを配置する](tag-small-value.png)
*図2: 16未満の値のビットレイアウト*

2048（`16#800`）未満の値では、ビット3が1にセットされます。これは継続バイトが1つ使われることを示し、値の上位3ビットはこのバイトのビット5〜7に延びます。

![2048未満の値のビットレイアウト。継続バイトを使うことを示すビット3と4に01を置く](tag-medium-value.png)
*図3: 2048未満の値のビットレイアウト（継続バイト1つ）*

さらに大きな値や負の値は、まずバイト列に変換されます。値が2〜8バイトに収まる場合、ビット3と4が1にセットされ、ビット5〜7には値のバイト数から2を引いたサイズが格納され、続けて値本体が置かれます。

![2〜8バイトの値のビットレイアウト。ビット3と4に11を置き、上位3ビットにバイト数-2を格納する](tag-large-value.png)
*図4: 2〜8バイトの値のビットレイアウト*

値が8バイトを超える場合は、ビット3〜7がすべて1にセットされます。続けて、バイト数から9を引いた値をネストされた符号なしリテラル（`beam_opcodes.hrl` のマクロ `?tag_u`）として符号化したものが置かれ、そのあとにデータ本体が続きます。

![8バイトを超える値の符号化。1バイト目のビット3から7をすべて1にし、続けてネストされたリテラル(Size-9)、そのあとにデータ本体が続く](tag-nested-literal.png)
*図5: 8バイトを超える値の符号化（ネストされたリテラルによるサイズ指定）*

<a id="_tag_types"></a>

#### タグの種類

コンパクトターム形式を読み取る際、得られる整数は `Tag` の値によって解釈のされ方が変わります。

- リテラルの場合、値はリテラルテーブル内のインデックスです。
- アトムの場合、値はアトムインデックスから1を引いたものです。値が0の場合は `NIL`（空リスト）を意味します。
- ラベルの場合、0は無効な値を意味します。
- タグが文字の場合、値は符号なしのUnicodeコードポイントです。
- タグ`Extended List`は、タームの組を含みます。`Size`を読み、`Size`要素のタプルを作り、そこに`Size/2`個の組を読み込みます。各組は`Value`と`Label`から成り、`Value`は比較対象のターム、`Label`は一致した場合のジャンプ先です。これは`select_val`命令で使われます。

{{< message >}}
型ヒントの追加により、コンパイラとJITがより緊密に連携できるようになり、OTP 25以降ではランタイム性能がより最適化されています。
{{< /message >}}

符号化の詳細については、`compiler` アプリケーションの `beam_asm:encode/2` を参照してください。タグの値はこの節で示したものですが、`beam_makeops` によって生成される `compiler/src/beam_opcodes.hrl` でも確認できます。

