Java Hello World, LLVM Edition

2025/12/07 20:51

Java Hello World, LLVM Edition

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

要約

Japanese Translation:

記事は、Java 開発者が新しい Foreign Function & Memory API を使って、従来の JNI を回避しつつ、Java コードから直接 LLVM 中間表現(IR)をビルド・実行・JIT コンパイルする方法を説明しています。Ubuntu/Debian に

llvm.sh
スクリプトで LLVM 20 をインストールし、
jextract
でネイティブバインディングを生成して
src/main/java
com.example.llvm.LLVM
クラスを作成する手順を案内します。また、Java 25 用に Maven プロジェクトを設定します。

チュートリアルでは、まず

main
関数を定義した LLVM モジュールを作り、基本ブロックを追加し、外部 C の
puts
関数を呼び出す命令を構築して、グローバル文字列定数
"Hello, World!"
を渡します。生成された IR は
LLVMPrintModuleToString
で表示され、LLVM のインタープリター(
lli
)で実行して「Hello, World!」と出力されることを確認します。

メモリ処理は FFM アーケーンを使い、

MemorySegment
s を Java の文字列に変換する例が示されています。JIT コンパイルでは、x86 Linux 用の MCJIT エンジンを初期化し、
LLVMCreateJITCompilerForModule
でモジュールをオンデマンドコンパイルします。グローバル関数へのポインタは
LLVMGetPointerToGlobal
で取得し、
Linker.nativeLinker().downcallHandle
を使ってメソッドハンドルを作成し、JNI を介さずに Java から JIT コンパイルされた
main
を呼び出します。最終的な実行例では、メソッドハンドル経由で直接「Hello, World!」が印字されます。

さらに追加の LLVM 命令(テキスト出力や算術演算など)を試すことを奨励し、GitHub 上の完全なソースコードへのリンクも示しています。この手法により、Java アプリケーションは JNI の複雑さを回避して LLVM のパフォーマンスと柔軟性を活用できるようになり、動的コード生成やクロスプラットフォームコンパイルの可能性が広がります。

本文

Java Advent – LLVM を使って FFM API 経由で “Hello, World!” を作る


概要

  • 目的: Java から LLVM IR を生成し実行して「Hello, World!」を出力する。
  • ツール:
    • Java ≥ 22(FFM API)
    • LLVM 20+(共有ライブラリとヘッダー)
    • jextract
      – LLVM C API の Java バインディング生成
    • Maven – プロジェクト構築

前提条件

# Ubuntu/Debian に LLVM 20 をインストール
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
./llvm.sh 20

libLLVM-20.so
とヘッダーが
/usr/include/llvm-c-20
にあることを確認してください。


プロジェクト設定

mvn archetype:generate \
    -DgroupId=com.example \
    -DartifactId=jvm-llvm-helloworld \
    -DarchetypeArtifactId=maven-archetype-quickstart \
    -DinteractiveMode=false

pom.xml
を編集:

<properties>
  <maven.compiler.source>25</maven.compiler.source>
  <maven.compiler.target>25</maven.compiler.target>
</properties>

スタートアップアプリをビルド&実行して Java が動作することを確認します。


LLVM バインディングの生成

jextract を使用(JDK の

src.zip
jextract
バイナリから取得):

jextract \
  -l LLVM-20 \
  -I /usr/include/llvm-c-20 \
  -I /usr/include/llvm-20 \
  -t com.example.llvm \
  --output src/main/java \
  --header-class-name LLVM \
  /usr/include/llvm-c-20/llvm-c/Core.h \
  /usr/include/llvm-c-20/llvm-c/Support.h \
  /usr/include/llvm-c-20/llvm-c/ExecutionEngine.h \
  /usr/include/llvm-c-20/llvm-c/Target.h \
  /usr/include/llvm-c-20/llvm-c/TargetMachine.h

バインディングが正しく生成されたかテスト:

public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
        System.out.println("LLVM version: " + LLVM.LLVM_VERSION_STRING().getString(0));
    }
}

ネイティブアクセスフラグを付けて実行し、警告が出ないことを確認します。


モジュールの構築

try (Arena arena = Arena.ofConfined()) {
    // モジュール作成
    var module = LLVMModuleCreateWithName(arena.allocateFrom("hello"));

    // ---- 関数シグネチャ: int main() ----
    var int32Type   = LLVMInt32Type();
    var mainType    = LLVMFunctionType(int32Type, NULL, 0, 0);
    var mainFunc    = LLVMAddFunction(module,
                                      arena.allocateFrom("main"),
                                      mainType);

    // ---- エントリブロック ----
    var entry      = LLVMAppendBasicBlock(mainFunc, arena.allocateFrom("entry"));
    var builder    = LLVMCreateBuilder();
    LLVMPositionBuilderAtEnd(builder, entry);

    // ---- グローバル文字列 "Hello, World!" ----
    var helloStr   = LLVMBuildGlobalStringPtr(
                         builder,
                         arena.allocateFrom("Hello, World!"),
                         arena.allocateFrom("hello_str"));

    // ---- puts(char*) を宣言 ----
    var paramTypes = arena.allocate(ADDRESS, 1);
    var charPtr    = LLVMPointerType(LLVMInt8Type(), 0);
    paramTypes.set(ADDRESS, 0, charPtr);

    var putsType   = LLVMFunctionType(int32Type, paramTypes, 1, 0);
    var putsFunc   = LLVMAddFunction(module,
                                      arena.allocateFrom("puts"),
                                      putsType);

    // ---- puts(helloStr) を呼び出し ----
    var callArgs   = arena.allocate(ADDRESS, 1);
    callArgs.set(ADDRESS, 0, helloStr);
    LLVMBuildCall2(builder, putsType, putsFunc, callArgs,
                   1, arena.allocateFrom("puts"));

    // ---- return 0 ----
    LLVMBuildRet(builder, LLVMConstInt(int32Type, 0, 0));

    // ---- 生成した IR を表示(任意) ----
    var irPtr = LLVMPrintModuleToString(module);
    System.out.println(irPtr.getString(0));
    LLVMDisposeMessage(irPtr);

    // ---- 後始末 ----
    LLVMDisposeBuilder(builder);
    LLVMDisposeModule(module);
}

実行すると次のように IR が出力されます。

; ModuleID = 'hello'
source_filename = "hello"

@hello_str = private unnamed_addr constant [14 x i8] c"Hello, World!\00", align 1

define i32 @main() {
entry:
  %call0 = call i32 @puts(ptr @hello_str)
  ret i32 0
}

この IR を

lli
にパイプすると「Hello, World!」が表示されます。


JIT コンパイル&Java から実行

IR の生成直後、クリーンアップ前に以下を追加します:

// ---- LLVM JIT 初期化 (x86) ----
LLVMLinkInMCJIT();
LLVMInitializeX86Target();
LLVMInitializeX86TargetInfo();
LLVMInitializeX86TargetMC();
LLVMInitializeX86AsmPrinter();
LLVMInitializeX86AsmParser();

// ---- 実行エンジン作成 ----
var jit = arena.allocate(ADDRESS);
var errPtr = arena.allocate(ADDRESS);
LLVMCreateJITCompilerForModule(jit, module, 2, errPtr);

// エンジンと main のアドレス取得
var execEngine = jit.get(ADDRESS, 0);
var mainAddr   = LLVMGetPointerToGlobal(execEngine, mainFunc);

// ---- コンパイル済み関数へのメソッドハンドルを作成 ----
var mainHandle = Linker.nativeLinker().downcallHandle(
        mainAddr,
        FunctionDescriptor.of(JAVA_INT)); // 引数なし、int を返す

// ---- 呼び出し ----
int ret = (int) mainHandle.invoke();
System.out.println("main() returned: " + ret);

これでプログラムは次のように出力します。

Hello, World!
main() returned: 0

外部インタプリタを使わず、ネイティブマシンコードとして直接実行されます。


次のステップ

  • 異なる LLVM 命令(算術演算、分岐など)を試す。
  • Java だけでより大きなプログラムを生成する。
  • ターゲットを ARM、macOS、Windows 等に変更して実行エンジンを切り替える。
  • jextract
    を使って追加の LLVM モジュールや自前の C ライブラリをバインドする。

同じ日のほかのニュース

一覧に戻る →

2025/12/08 2:18

I failed to recreate the 1996 Space Jam website with Claude

## Japanese Translation: ## 要約 著者は、Claude AI を使って 1996 年の Warner Bros の「Space Jam」ランディングページをスクリーンショットとアセットフォルダから再構築しようとしました。元のサイトは 200 KB 未満の単一 HTML ファイルで、絶対位置決め、テーブルレイアウト、およびタイル状の星空 GIF 背景に依存しています。 **プロセスと所見** 1. **初期試行:** Claude は概算レイアウトを生成しましたが、惑星軌道を誤った位置に配置しました。軌道パターンは認識できたものの、それを再現することには失敗しました。 2. **構造化プロンプト:** 著者は Claude に「知覚分析」「空間解釈」「再構築計画」の各セクションで理由を説明させ、正確なピクセル座標を要求しましたが、Claude はそれらを提供できませんでした。 3. **カスタムツール:** 精度向上のために 50 px → 5 px のグリッドオーバーレイ、ラベル付き座標参照点、色差比較、スクリーンショットサイドバイサイドビューア、およびスクリーンショットを 6 区域に分割するスクリプトを構築しました。 4. **結果:** Claude の調整は目標から 5–10 px 内に留まりましたが、正しい軌道半径(約 350–400 px)には決して収束しませんでした。内部レイアウトが生成されると、その後のフィードバックは元のスクリーンショットではなく、この誤ったモデルに基づいて行われました。 5. **トークナイズ仮説:** 著者は Claude が 16×16 パッチで画像をトークナイズしているため、細かい視覚的粒度が欠如し、セマンティック理解はあるもののピクセル精度が低いと考えました。 6. **ズームインテスト:** 200 % に拡大したスクリーンショットを提供して、大きなパッチで解像度が向上するか確認しましたが、Claude は依然として比例スケーリング指示に従いませんでした。 **結論** このタスクは未解決のままです。実験は Claude の空間推論限界をベンチマークとし、ピクセル単位で正確な画像再構築におけるモデルの現在の制約を示しています。

2025/12/08 7:18

How I block all online ads

## Japanese Translation: > **概要:** > 著者は、ウェブブラウザとモバイルアプリの両方で広告を排除するために長期的かつ多層的なアプローチを説明しています。彼は **Firefox + uBlock Origin** と最小限のフィルタリスト(組み込みのuBlockフィルタ、EasyList、AdGuard – Ads)と「広告でない不快要素」のためのカスタム非広告フィルタを使用します。 > DNS フィルタリングには **Pi‑hole(または AdGuard Home)** を Docker 上で $5 の DigitalOcean ドロップレットに稼働させ、WireGuard VPN の DNS サーバとして設定しています。トラフィックは **クラウドベースの VPN**(DigitalOcean、Hetzner、Azure、Google Cloud、または AWS)を経由し、プラットフォームが公的クラウド IP を検知して広告配信を減らします。 > この設定では **Cloudflare のキャプチャや HTTP エラー** が発生する場合があるため、著者は該当サイトで VPN を無効化しています。また、**Consent‑O‑Matic**(クッキーポップアップ)、**Buster**(キャプチャ)、**SponsorBlock**(動画広告)などのブラウザ拡張機能を推奨します。iOS では **Background App Refresh** をオフにするとデータ収集が減少し、Android では **ReVanced がアプリをパッチできますが、セキュリティリスクがあります** と指摘しています。 > 著者はこの統合戦略を 3 年以上使用しており、現在ほとんど広告を見ることはありません。プラットフォーム別の効果は異なります:YouTube は uBlock Origin + VPN(1週間〜1か月)が必要;Instagram は uBlock Origin のみで十分;Twitch は主に VPN に依存し、数日で効果が現れます;TikTok は両方のツールを使用しますが、数時間だけです。**AdMob** を利用するアプリも DNS ブロックの恩恵を受けます。 > 広告配信ネットワークは数日から数週間でパターンを観察し調整する可能性があるため、継続的な監視が必要です。著者は **Firebog** をブロックリストの良い情報源として引用し、正当なサイトを壊さないように許可リスト(allowlist)を維持する重要性を強調しています。

2025/12/07 23:37

Dollar-stores overcharge cash-strapped customers while promising low prices

## Japanese Translation: ドルジェネラルとファミリードラーは、棚に貼られたタグの価格よりも高い価格で顧客を頻繁に請求し、低所得層の買い物客に不釣り合いな過剰課金が広く発生しています。州検査と独立調査では、一部店舗でエラー率が88%に達するケースや、両チェーン全体で価格設定失敗が一貫して報告されています。 主な例としては、ノースカロライナ州ウィンザーのファミリードラーで23%のスキャンアイテムが過剰請求(同店の4回連続失敗)、オハイオ州ハミルトンのドルジェネラルで76%のエラー率(2022年10月)、ニュージャージー州バウンドブルックのファミリードラーで68%の不一致(2023年2月)があります。2022年1月以降、ドルジェネラルは4,300件以上、ファミリードラーは2,100件以上の価格失敗事例を記録しています。 アリゾナ州(60万ドル)、コロラド州(40万ドル)、ニュージャージー州・バーモント州・ウィスコンシン州・オハイオ州(最大100万ドル)など複数の州がチェーンと訴訟を和解し、連邦および州の司法長官は追加訴訟を提起しています。株主訴訟では、経営陣がシステム的問題を認識していたと主張されています。ニュージャージー州の連邦裁判所は、モバイルアプリ利用に関連する仲裁条項を理由にドルジェネラルに対する集団訴訟を停止し、消費者の救済手段を制限しました。 規制当局は現在の1検査あたり5,000ドル上限を超えるより厳格な執行や高い罰則を課すことができ、さらに州が調査を進めるにつれて追加の和解が生じる可能性があります。影響としては顧客信頼の低下、チェーンへの潜在的財務損失、評判へのダメージ、およびドルストア業界全体での価格設定と人員管理の強化への動きが挙げられます。