ABIの安定性
はじめに
アプリケーションバイナリインターフェース(ABI)とは、プログラムが他のコンパイル済みプログラムから関数を呼び出し、データ構造を使用するための方法です。これは、アプリケーションプログラミングインターフェース(API)のコンパイルされたバージョンです。言い換えれば、アプリケーションが目的のタスクを実行できるようにするクラス、関数、データ構造、列挙、および定数を記述するヘッダーファイルは、コンパイルによって、ABIのプロバイダーがコンパイルされたアドレスと期待されるパラメータ値、およびメモリ構造のサイズとレイアウトのセットに対応します。
ABIを使用するアプリケーションは、利用可能なアドレス、期待されるパラメータ値、およびメモリ構造のサイズとレイアウトが、ABIプロバイダーがコンパイルされたものと一致するようにコンパイルする必要があります。これは通常、ABIプロバイダーが提供するヘッダーに対してコンパイルすることによって実現されます。
ABIのプロバイダーとABIのユーザーは、異なるコンパイラのバージョンで異なるタイミングでコンパイルされる可能性があるため、ABIの互換性を保証する責任の一部はコンパイラにあります。異なるベンダーから提供されたコンパイラの異なるバージョンは、すべて特定のコンテンツを持つヘッダーファイルから同じABIを生成する必要があり、特定のヘッダーで記述されたAPIに、ヘッダーの説明から生じるABIの規約に従ってアクセスするアプリケーションのコードを生成する必要があります。最新のコンパイラは、コンパイルするアプリケーションの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 に、複数の Node.js メジャーバージョンにわたって安定した ABI を提供する API を装備することへの要求が高まっています。そのような API を作成する動機は次のとおりです。
JavaScript 言語は、ごく初期の頃からそれ自体との互換性を維持していますが、JavaScript コードを実行するエンジンの ABI は、Node.js のメジャーバージョンごとに変化します。これは、完全に JavaScript で書かれた Node.js パッケージで構成されるアプリケーションは、そのようなアプリケーションが実行される本番環境に新しいメジャーバージョンの Node.js がドロップされても、再コンパイル、再インストール、または再デプロイする必要がないことを意味します。対照的に、アプリケーションがネイティブアドオンを含むパッケージに依存している場合、新しいメジャーバージョンの Node.js が本番環境に導入されるたびに、アプリケーションを再コンパイル、再インストール、および再デプロイする必要があります。ネイティブアドオンを含む Node.js パッケージと、完全に JavaScript で書かれた Node.js パッケージとの間のこの不均衡は、ネイティブアドオンに依存する本番システムのメンテナンスの負担を増大させています。
他のプロジェクトは、本質的に 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 の N-API は、公開された Node.js のメジャーバージョン、および後続のすべての Node.js バージョン (後続のメジャーバージョンを含む) で利用可能になります。
ネイティブアドオンの作成者は、アドオンが node_api.h
で定義された API と node_api_types.h
で定義されたデータ構造と定数のみを使用するようにすることで、N-API の前方互換性の保証を利用できます。そうすることで、作成者は、アプリケーションのメンテナンスの負担が、プロジェクトにネイティブアドオンを追加することによって、純粋に JavaScript で書かれたパッケージを追加するよりも増えないことを本番環境のユーザーに示すことで、アドオンの採用を促進します。
新しい API が随時追加されるため、N-API はバージョン管理されています。セマンティックバージョニングとは異なり、N-API のバージョン管理は累積的です。つまり、N-API の各バージョンは、semver システムのマイナーバージョンと同じ意味を持ちます。つまり、N-API に加えられたすべての変更は下位互換性があります。さらに、新しい N-API は、コミュニティが本番環境で検証する機会を得るために、実験的なフラグの下に追加されます。実験的なステータスとは、新しい API が将来 ABI 非互換な方法で変更する必要がないように注意が払われていますが、設計どおりに正しく有用であることが本番環境で十分に証明されていないことを意味し、そのため、今後のバージョンの N-API に最終的に組み込まれる前に、ABI 非互換な変更を受ける可能性があります。つまり、実験的な N-API はまだ前方互換性の保証の対象ではありません。