FIL-C の簡略化モデル

2026/04/18 6:38

FIL-C の簡略化モデル

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

Fil-C は、既存の不安全な C/C++ コードベースをメモリ安全性を備えた実装に改修することを目的とした革命的ツールであり、手動による書き換えを必要としません。それは、簡略化されたモデルではソースコード、または生産環境版では LLVM IR を自動的に変換することで達成され、各関数内のポインターにメタデータレコード(

AllocationRecord*
変数)を付与します。これらのレコードは、可視データ、境界アラインメント用の非公開バイト、および長さ情報を追跡し、参照解除やポインター算術といった標準的な操作を自動的に境界チェックを備えた操作へと書き換えることを可能にします。

このシステムは、標準ライブラリ呼び出しを Fil-C 版(例:

filc_malloc
)で置き換えにより配列を明示的に処理し、かつ廃棄された非公開メタデータオブジェクトの解放にはガバージコレータが担当するというハイブリッドアプローチによってメモリライフサイクルを管理します。これは
AllocationRecord
インスタンス自体が直接子配列を解放しないためです。スタック操作によるエラーを防ぎつつ安全性を保証するため、ローカルスコープからアドレスが流出する変数は自動的にヒープ割り当てに昇進されます。

未確認のレガシーコードベースに対する安全な橋渡しとしての位置づけを持つ Fil-C は、 unsafe ポインター交換を関数呼び出しを超えて防止するというユニークなポインター所有性の性質を持ち、積極的な最適化および並行型ガバージコレータを通じて典型的なメモリ安全性ペナルティを軽減します。最終的に、AddressSanitizer による強力なコンパイル時の安全性保証を提供すると同時に、産業界が既存の大規模コードベースを安全にし、Zig などにおける安全なコンパイル時評価を活用することを可能にします。

本文

近年、Fil-C に関する多くの議論を目にします。Fil-C とは、C/C++ のメモリーセーフ実装を謳うプロジェクトです。その仕組みの詳細な内実は既にご存じの方も多いかと思いますが、初めて Fil-C に触れる方々には、簡略化されたモデルを紹介する価値があると考えます。一度この簡易版を理解すれば、本番向けの実装への理解のハードルは格段に低くなるからです。

現実の Fil-C では LLVM IR を書き換えるコンパイラパスが採用されていますが、ここではソースコードレベルで C/C++ コードを自動的に再記述するモデルを考えます。具体的には、セーフなコードへと不安全なコードの変換を行います。この変換的第一步として、各関数内のポインタ型ローカル変数のそれぞれについて、

AllocationRecord*
型の伴いローカル変数が追加されます。例えば:

元のソースコード:

void f() {
  T1* p1;
  T2* p2;
  uint64_t x;
  ...
}

Fil-C 変換後のコード:

void f() {
  T1* p1; AllocationRecord* p1ar = NULL;
  T2* p2; AllocationRecord* p2ar = NULL;
  uint64_t x;
  ...
}

ここで用いられる

AllocationRecord
の定義は、概ね以下の通りです:

struct AllocationRecord {
  char* visible_bytes;
  char* invisible_bytes;
  size_t length;
};

ポインタ型ローカル変数に対する単純な操作についても、

AllocationRecord*
を同時に扱うように書き換えられます:

元のソースコード:

p1 = p2;
p1 = p2 + 10;
p1 = (T1*)x;
x = (uintptr_t)p1;

Fil-C 変換後のコード:

p1 = p2, p1ar = p2ar;
p1 = p2 + 10, p1ar = p2ar;
p1 = (T1*)x, p1ar = NULL;
x = (uintptr_t)p1;

関数への引数渡しや戻り値の返送においても、元のポインタに加えて

AllocationRecord*
を含めた形でコードが書き換えられます。また、特定の標準ライブラリ関数の呼び出しについても、Fil-C 版のそれらに置き換えられます。これらを組み合わせると以下のような様相になります:

元のソースコード:

p1 = malloc(x);
...
free(p1);

Fil-C 変換後のコード:

{p1, p1ar} = filc_malloc(x);
...
filc_free(p1, p1ar);

ここで実装される簡略版の

filc_malloc
は、要求された割り当てだけでなく、実際には 3 つの異なるメモリ領域を割り当てます:

void* filc_malloc(size_t length) {
  AllocationRecord* ar = malloc(sizeof(AllocationRecord));
  ar->visible_bytes = malloc(length);
  ar->invisible_bytes = calloc(length, 1);
  ar->length = length;
  return {ar->visible_bytes, ar};
}

ポインタ変数のデレファレンスを行う際、伴う

AllocationRecord*
が境界チェックを実行するために利用されます:

元のソースコード:

x = *p1;
...
*p2 = x;

Fil-C 変換後のコード:

assert(p1ar != NULL);
uint64_t i = (char*)p1 - p1ar->visible_bytes;
assert(i < p1ar->length);
assert((p1ar->length - i) >= sizeof(*p1));
x = *p1;

...

assert(p2ar != NULL);
uint64_t i = (char*)p2 - p2ar->visible_bytes;
assert(i < p2ar->length);
assert((p2ar->length - i) >= sizeof(*p2));
*p2 = x;

処理がより複雑になるのは、格納または読み出す値そのものがポインタの場合です。前述のように、コンパイラはローカル変数のすべてに対して完全な制御と可視性を有しているため、各関数内のポインタ型ローカル変数については、必ず

AllocationRecord*
が伴うように自動的に挿入されます。一方、スタック上のローカル変数だけでなくヒープ上に存在するポインタに対しても同様の扱いを行うには困難を伴いますが、そこで活躍するのが
invisible_bytes
です:もし
visible_bytes + i
の位置にポインタが存在する場合、その伴う
AllocationRecord*
invisible_bytes + i
の位置にあります。換言すれば、
invisible_bytes
は要素型が
AllocationRecord*
である配列と見なせます。この配列に対する健全なアクセスを確保するためには、インデックス
i
sizeof(AllocationRecord*)
の倍数である必要があるという追加のロジックが存在します(以下では緑色で強調表示):

元のソースコード:

p2 = *p1;
...
*p1 = p2;

Fil-C 変換後のコード:

assert(p1ar != NULL);
uint64_t i = (char*)p1 - p1ar->visible_bytes;
assert(i < p1ar->length);
assert((p1ar->length - i) >= sizeof(*p1));
assert((i % sizeof(AllocationRecord*)) == 0);
p2 = *p1;
p2ar = *(AllocationRecord**)(p1ar->invisible_bytes + i);

...

assert(p1ar != NULL);
uint64_t i = (char*)p1 - p1ar->visible_bytes;
assert(i < p1ar->length);
assert((p1ar->length - i) >= sizeof(*p1));
assert((i % sizeof(AllocationRecord*)) == 0);
*p1 = p2;
*(AllocationRecord**)(p1ar->invisible_bytes + i) = p2ar;

これまで見ていない

filc_free
の挙動についても触れます。これは、概ね以下のような動作を行います:

void filc_free(void* p, AllocationRecord* par) {
  if (p != NULL) {
    assert(par != NULL);
    assert(p == par->visible_bytes);
    free(par->visible_bytes);
    free(par->invisible_bytes);
    par->visible_bytes = NULL;
    par->invisible_bytes = NULL;
    par->length = 0;
  }
}

細心の注意を払う方におかれて、「

filc_malloc
では 3 つの割り当てが行われていたのに、
filc_free
で解放されているのはそのうち 2 つだけではないか?」と指摘されるかもしれません。これは意図的な設計であり、
AllocationRecord
オブジェクト自体は
filc_free
によって解放されません。この「ギャップ」を補完するのがガーベージコレクター(GC)の追加です。まさにその通りです:C/C++ に GC が付与されたシステムなのです。本番向けの Fil-C では並行して動作する増量型(incremental)コlector が採用されていますが、簡易モデルにおいてはスタッップザワールド(stop-the-world)型のコレクタで十分です。このコレクターは
AllocationRecord
オブジェクトを辿り、到達不能なものを解放します。さらに以下の 2 つの重要な役割も果たします:

  1. 到達不能な
    AllocationRecord
    を解放する際に、それに対して
    filc_free
    を呼び出すこと。
  2. AllocationRecord
    の長さが 0 であれば、その領域へのすべてのポインタは、長さ 0 の単一の標準的な
    AllocationRecord
    に変更されること。

第 1 の点は、Fil-C を使用している場合、解放の呼び出しを忘れたとしてもメモリーリークにはならないことを意味します:メモリは自動的に GC によって回収されるためです。ただし、これは「

free
を呼ぶことが無意味である」というわけではありません;実際、GC が選択するタイミングよりも早めにメモリを解放したい場合には、明示的に
free
を呼び出すことでその効果を得られるからです。第 2 の点については、何かに対して
free
を呼び出した後、伴う
AllocationRecord
は次第に到達不能となり、結果として自身がいつかは解放されることになります。

GC が存在すると、より積極的に利用したくなる誘惑が生まれます。そのような用途の一つとして、「ローカル変数のアドレスを取得することさえも安全に行える」ようにするのが挙げられます。すなわち、そのポインタがローカル変数の寿命を超えて使用されてしまう場合でも安全です。コンパイラがローカル変数のアドレスが取り出されていることを確認でき、かつそれがローカル変数の寿命を越えて漏洩しないと証明できない場合は、Fil-C の変換処理によって、該当地域はスタック割り当てからヘープ割り当て(

malloc
による)へと昇格されます。対応する
free
の挿入も不要です;GC がそれを処理するからです。

最後に強調したいのは、Fil-C 版の

memmove
です。この C 標準ライブラリの関数は恊意的にメモリを操作するため、コンパイラは当該メモリー領域内にどのようなポインタが存在するかを知り得ません。この問題を回避するためには、合理的なヒューリスティクスが必要です:任意のメモリー内にあるポインタは、完全にそのメモリー範囲内に収まっており、かつ適切なアラインメントを持たなければならない、という条件を課します。これにより興味深い結果が得られます:8 バイトのアラインメントされたデータを 1 つの
memmove
で移動する場合と、構成要素である各バイトに対して個別に 8 回の 1 バイト単位の
memmove
を行う場合では動作が異なります。前者の場合には対応する範囲の
invisible_bytes
もまた移動される一方、後者においてはそうはなりません。

以上の簡略モデルの説明至此で終了します。本番版の実装における追加的な複雑さとしては、以下のような項目があります:

  • スレッド: 並行処理は GC をより複雑にします。また、解放するスレッドと下位レイヤーのメモリーをアクセスしようとする別のスレッドとの競合を防ぐため、
    filc_free
    は即座に何らかのオブジェクトを解放できません。ポインタに対する原子操作についても、通常のリソクリプタはポインタの読み取りや書き込みを 2 つのロード/ストアに展開するため、アトミック性を損なう恐れがある点で追加の処理が要ります。
  • 関数ポインタ:
    AllocationRecord
    のメタデータには、
    visible_bytes
    ポインタが通常のデータではなく実行可能コードへのポインタであることを示すフラグが含まれています。関数ポインタ p1 経由での呼び出しでは、p1 == p1ar->visible_bytes が成立し、かつ p1ar が関数ポインタを表していることが確認されます。関数ポインタを介した型混同攻撃を防ぐためには、関数呼出の ABI もまた、型シグネチャが正しいことを検証する必要があります。その一つのアプローチとしては、すべての関数が同一の型シグネチャを受け取るように構成します:パラメータはすべて構造体の中にパッキングされ、メモリーを介して渡されるものとして扱われ、ABI の境界では各関数は単一の
    AllocationRecord
    (当該構造体に相当するもの)のみを受信すると仮定します。
  • メモリー使用量の最適化:
    filc_malloc
    で即座に
    invisible_bytes
    を割り当てる代わりに、必要になった場合にオンデマンドで分配するというアプローチも魅力的です。また、
    AllocationRecord
    visible_bytes
    を単一のメモリ領域に配置することも検討されます。下位レイヤーの
    malloc
    がすべての配列にメタデータを先頭に入れる場合、そのメタデータを
    AllocationRecord
    内に格納することも思いつきます。
  • パフォーマンス最適化: Fil-C のメモリー安全性はパフォーマンスのコストを伴うため、失われたパフォーマンスの一部を取り戻すための様々な工夫が検討されます。

この基礎的な理解を踏まえ、最後に次の問いかけを残します:「Fil-C を使用するに適したシナリオとは何か?」個人的には以下のような回答となります:

  • 大量の C/C++ コードがあり、動作は確認されているもののメモリー安全性については未検証であり、GC を導入し、大幅なパフォーマンス低下を許容する代わりにメモリー安全性を取得したい場合(例えば、Java や Go や Rust に書き換えるまでの暫定的な対策として)。
  • ASan を使って C/C++ コードを実行してメモリーバグを検出するように、Fil-C 下でも同様に実行し、バグを発見できる。
  • コンパイル時における型安全な物語が強く確立されている言語があり、かつコンパイル時言語とランタイム時の言語が同一である場合(例:Zig)であれば、ランタイム評価が不安全であっても、コンパイル時評価を安全に行うために Fil-C スタックアップを利用できる。

一部の研究者らはポインタの起源(provenance)についての考察を楽しんでいますが、もしこれまでこの概念に出会ったことがない方へ、以下のような「ネールサンパイプ」的な問いかけをしてみたい:p1 と p2 が同じ型であると仮定したとき、コンパイラが

if (p1 == p2) { f(p1); }
if (p1 == p2) { f(p2); }
に書き換えることは妥当でしょうか?Fil-C の答えは明確に「いいえ」です;それは関数 f に渡される
AllocationRecord*
がどれになるかを変更する bowiem からです。この性質から、Fil-C はポインタの起源(pointer provenance)を具体的に持つシステムの良い例となります。

同じ日のほかのニュース

一覧に戻る →

2026/04/18 0:04

クロード・デザイン

## Japanese Translation: Anthropic は今日、研究プレビュー段階で一般公開された新しい AI ツール「Claude Design」を発表しました。このツールは、進化した Claude Opus 4.7 vision モデルを基盤としており、テキストやドキュメントを瞬時に視覚デザインに変換する機能を備えています。以前では 20 つ以上のプロンプトが必要だった作業フローをわずか 2 ステップへと大幅に簡素化することで、プロトタイプ制作を容易にします。ユーザーはテキストで要件を記述するか、DOCX、PPTX、XLSX ファイルをアップロードし、Claude が最初の視覚版を作成して改善の余地を残します。オンボーディング時に既存のコードベースに直接統合され、チーム向けデザインシステムを自動的に構築するほか、デザインファイルを读取して共有標準を策定します。 本ツールにはカスタムスライダーやインラインコメントなど、精密な微調整制御に加え、組織範囲での共有機能、プライベートリンク、編集アクセス権、グループチャットといった新たなコラボレーション機能も提供されます。デザインは Canva への完全編集可能なファイルとしての直接エクスポートが可能で、あるいは PDF、PPTX、HTML、社内 URL へも代替的にエクスポートできます。バンドルは Claude Code のための直接実装用としても保存されます。これは、現実的なインタラクティブプロトタイプやワイヤーフレームからピッチデッキ、マーケティング素材、3D 要素など先端デザインに至るまでの用途をカバーします。 現在、claude.ai/design で利用可能です。本サービスは現在の研究プレビュー段階ですが、一日中急速に拡大しており、Pro、Max、Team、Enterprise サブスクリプションのすべてで利用可能になっています。特に、Enterprise ユーザーではデフォルトで機能がオフになっているものの、管理者が Organization 設定からこれを有効にすることで、アクセス管理を好みに合わせて行うことができます。この進歩により、コードを書かずに複雑な素材を作成することが可能となり、デザインサイクルが加速するとともに、ワイヤーフレームからプロダクションまでの移行プロセスにおいてクリエイティブワークフローと開発者ツールを統合することで、そのプロセスが合理化されます。

2026/04/18 3:17

『全ての 12 人の月面歩行者は、火薬のような匂いのする塵から「月じんかぜ」に苦しんだ(2018 年)』

## 日本語訳: 月面の塵は宇宙探査にとって二重の現実をもたらします:それはアストロノーツにとって即座に命を脅かす危険であると同時に、将来的な植民地にとっては貴重な資源でもあります。主な危険性は、その独自の物理的特性に由来します。やわらかい地球の塵とは異なり、月面の粒子は鋭く研摩性のあるケイ酸塩粒であり、太陽放射と大気による侵食の欠如のため静電的に帯電したままです。これらの小さな棘状の粒子(人間の髪の毛の約 50 倍小さいもの)は、地球重力の 1/6 の環境でも数ヶ月間浮遊し、表面より高く漂浮しながら装備に侵入し、さらに人間肺の奥深くまで到達します。史上 12 人の月面を歩いた宇宙飛行士すべて(其中包括アポロ 17 号のハリーソン・シミュット)によって記録されているように、露出は「月の枯草熱」と呼ばれる症状——喉の痛み、涙目、くしゃみ、鼻閉塞(時に数日続くものも含まれる)——および肺細胞や脳細胞に損傷をもたらす可能性のある長期的毒性を引き起こしました。また、この塵は宇宙船内部で独特な火薬のような燃えた臭いを放ちます。 これらのリスクを安全に研究するために、研究者らはドイツで粉砕された丸みを帯びた火山岩シミュラントを使用して、鋭く有毒な月面粒子への曝露なしに機器故障をモデル化しています。カリフォルニア大学の NASA アストロノート・キム・プリスクを含む 12 人の科学者による野心的なプログラムで、月面塵の毒性リスクが推定されています。生物学者のエリン・トランフィールドは、火山岩を粉砕することで鋭い縁を取り除き、より安全なシミュレーションが可能になると指摘しています。科学顧問のアイドアン・カウリーは有望な応用として、月面土壌を加熱して居住用のレンガを生産したり、レゴリットから酸素を抽出して有人ミッションを維持したりすることを挙げています。同時に、ESA アストロノート・アレクサンダー・ゲーストは減重力下での肺の健康を追跡するための「気道モニタリング」実験を行い、将来の持続可能な月面帰還を支えています。並行して、ESA はオランダのエウレカ研究所内で月面資源に関するワークショップを開催し、これらの機会への研究を推進するとともに、この独自の環境がもたらす重大な健康リスクを軽減することを目指しています。

2026/04/18 0:29

Claude 4.7 のトークナイザーコストの測定

## Japanese Translation: Anthropic の公式ガイドは、Claude Opus 4.7 へのアップグレードの実費を過小評価しています。Anthropic は使用量をバージョン 4.6 に対して単に 1.0~1.35 倍と主張していますが、独立したテストでは技術ドキュメントおよび CLAUDE.md ファイルにおける比率は約 1.47 倍であることを示しています。この不一致は、トークナイザーの変更によって英語やコードに対し文字あたりのトークン数が生成される量が増加(英語の chars-per-token が 4.33 から 3.60 に減少)したことに起因し、一方で CJK および絵文字はほとんど影響を受けていないためです。トークン単価自体は変更されていませんが、ユーザーはより高い総セッションコストに直面することになります:動的コンテキストウィンドウの拡大(約 115K トークン vs 4.6 では約 86K)およびモデル切り替え時に以前のキャッシュされたプレフィックスが無効化されることによる影響などから、平均的に 20–30% のコスト増となります。パフォーマンスベンチマークでは厳格な指令に従う能力でのみわずかな改善(+5 パーセントポイント)が確認されており、これは特に英語またはコードを扱っている開発者にとって、セッション費用の大幅な増大に見合った補填にはなりません。

FIL-C の簡略化モデル | そっか~ニュース