ABI 安定性
はじめに
アプリケーションバイナリインターフェース (ABI) は、プログラムが他のコンパイル済みプログラムから関数を呼び出したり、データ構造を使用したりするための方法です。これは、アプリケーションプログラミングインターフェース (API) のコンパイル済みバージョンです。つまり、アプリケーションが目的のタスクを実行できるようにするクラス、関数、データ構造、列挙型、および定数を記述したヘッダーファイルは、コンパイルによって、ABI の提供者がコンパイルされたときのアドレスのセット、期待されるパラメータ値、メモリ構造のサイズとレイアウトに対応します。
ABI を使用するアプリケーションは、利用可能なアドレス、期待されるパラメータ値、メモリ構造のサイズとレイアウトが、ABI の提供者がコンパイルされたときのものと一致するようにコンパイルされなければなりません。これは通常、ABI 提供者が提供するヘッダーファイルに対してコンパイルすることで実現されます。
ABI の提供者と ABI の利用者は、異なる時期に異なるバージョンのコンパイラでコンパイルされる可能性があるため、ABI の互換性を確保する責任の一部はコンパイラにあります。異なるバージョンのコンパイラ、場合によっては異なるベンダーから提供されるものであっても、特定のコンテンツを持つヘッダーファイルから同じ ABI を生成し、ヘッダー内の記述から生じる ABI の規約に従って、所与のヘッダーで記述された API にアクセスするコードをアプリケーション用に生成しなければなりません。現代のコンパイラは、コンパイルするアプリケーションの ABI 互換性を損なわないという点で、かなり良い実績を持っています。
ABI 互換性を確保する残りの責任は、コンパイル時に安定を保つべき ABI となる API を提供するヘッダーファイルを維持するチームにあります。ヘッダーファイルに変更を加えることはできますが、コンパイル時に、既存の ABI 利用者が新しいバージョンと互換性がなくなるような方法で ABI が変更されないように、変更の性質を注意深く追跡する必要があります。
Node.js における ABI 安定性
Node.js は、いくつかの独立したチームによって維持されているヘッダーファイルを提供しています。例えば、node.h や node_buffer.h のようなヘッダーファイルは Node.js チームによって維持されています。v8.h は V8 チームによって維持されており、Node.js チームと密接に協力していますが、独自のスケジュールと優先順位を持つ独立したチームです。したがって、Node.js チームは、プロジェクトが提供するヘッダーに導入される変更を部分的にしか制御できません。その結果、Node.js プロジェクトは セマンティックバージョニング を採用しました。これにより、プロジェクトが提供する API は、1つのメジャーバージョン内でリリースされるすべてのマイナーおよびパッチバージョンの Node.js に対して安定した ABI をもたらすことが保証されます。実際には、これは Node.js プロジェクトが、特定のメジャーバージョンの Node.js に対してコンパイルされた Node.js ネイティブアドオンが、コンパイルされたメジャーバージョン内の任意の Node.js マイナーまたはパッチバージョンによってロードされたときに正常にロードされることを保証することにコミットしたことを意味します。
N-API
複数の Node.js メジャーバージョンにわたって安定した ABI をもたらす API を Node.js に装備することへの需要が高まっています。このような API を作成する動機は次のとおりです。
-
JavaScript 言語は非常に初期の頃から自己互換性を保ってきましたが、JavaScript コードを実行するエンジンの ABI は Node.js のメジャーバージョンごとに変更されます。これは、完全に JavaScript で書かれた Node.js パッケージで構成されるアプリケーションは、新しいメジャーバージョンの Node.js がそのようなアプリケーションを実行する本番環境に導入されても、再コンパイル、再インストール、または再デプロイする必要がないことを意味します。対照的に、アプリケーションがネイティブアドオンを含むパッケージに依存している場合、新しいメジャーバージョンの Node.js が本番環境に導入されるたびに、アプリケーションを再コンパイル、再インストール、および再デプロイする必要があります。ネイティブアドオンを含む Node.js パッケージと、完全に JavaScript で書かれたパッケージとのこの格差は、ネイティブアドオンに依存する本番システムのメンテナンス負担を増大させています。
-
他のプロジェクトは、本質的に Node.js の代替実装である JavaScript インターフェースの作成を開始しました。これらのプロジェクトは通常、V8 とは異なる JavaScript エンジン上に構築されているため、そのネイティブアドオンは必然的に異なる構造を取り、異なる API を使用します。それにもかかわらず、Node.js JavaScript API の異なる実装間でネイティブアドオンに単一の API を使用することで、これらのプロジェクトは Node.js の周りに蓄積された JavaScript パッケージのエコシステムを活用できるようになります。
-
Node.js は将来、異なる JavaScript エンジンを含む可能性があります。これは、外部的にはすべての Node.js インターフェースは同じままであるが、V8 ヘッダーファイルは存在しなくなることを意味します。このような措置は、JavaScript エンジンに依存しない API が最初に Node.js によって提供され、ネイティブアドオンによって採用されなければ、Node.js エコシステム全般、特にネイティブアドオンのエコシステムを混乱させる原因となります。
これらの目的のために、Node.js はバージョン 8.6.0 で N-API を導入し、Node.js 8.12.0 時点でプロジェクトの安定したコンポーネントとしてマークしました。この API は node_api.h と node_api_types.h ヘッダーで定義されており、Node.js のメジャーバージョン境界を越える前方互換性の保証を提供します。この保証は次のように述べることができます。
N-API の特定のバージョン *n* は、それが公開された Node.js のメジャーバージョン、およびその後のすべての Node.js のバージョン(その後のメジャーバージョンを含む)で利用可能になります。
ネイティブアドオンの作成者は、アドオンが node_api.h で定義された API と node_api_types.h で定義されたデータ構造と定数のみを使用するようにすることで、N-API の前方互換性の保証を活用できます。そうすることで、作成者は本番環境のユーザーに対し、ネイティブアドオンをプロジェクトに追加しても、純粋に JavaScript で書かれたパッケージを追加する場合以上にアプリケーションのメンテナンス負担が増加しないことを示すことで、アドオンの採用を促進します。
N-API は、新しい API が随時追加されるため、バージョン管理されています。セマンティックバージョニングとは異なり、N-API のバージョニングは累積的です。つまり、N-API の各バージョンは、semver システムのマイナーバージョンと同じ意味を持ち、N-API に加えられたすべての変更は後方互換性があることを意味します。さらに、新しい N-API は、コミュニティが本番環境で検証する機会を与えるために、実験的なフラグの下で追加されます。実験的ステータスとは、新しい API が将来 ABI 非互換な方法で変更される必要がないように注意が払われているものの、設計どおりに正しく有用であることが本番環境でまだ十分に証明されておらず、そのため、最終的に今後の N-API のバージョンに組み込まれる前に ABI 非互換な変更を受ける可能性があることを意味します。つまり、実験的な N-API はまだ前方互換性の保証の対象外です。