---
title: Foreign Function & Memory (FFM) APIでMoonBitの関数をJavaで呼び出すメモ
summary: この記事では、MoonBitで実装した竹内関数をJava 22のFFM APIで呼び出す手順と性能比較を紹介します。
tags: ["Java", "FFM", "MoonBit"]
categories: ["Programming", "Java", "java", "lang", "foreign"]
date: 2025-12-15T01:03:42Z
updated: 2025-12-15T01:07:09Z
---

[こちらの記事](/entries/872)でJava 22で正式版となったForeign Function & Memory（FFM）APIを使用して、Rustで実装した関数をJavaから呼び出すサンプルを試しましたが、
今回は[MoonBit](https://www.moonbitlang.com/)で実装した関数をNativeビルドでエクスポートし、Javaから呼び出してみました。

> [!NOTE] MoonBit自体は、WASM、JavaScript、Native、LLVMと様々なターゲットにビルド可能です。

題材は前回と同じく、[竹内関数](https://ja.wikipedia.org/wiki/%E7%AB%B9%E5%86%85%E9%96%A2%E6%95%B0) を使いました。

MoonBit自体は初めて触るので、この記事ではインストールからメモします。

**目次**
<!-- toc -->

### 検証環境

以下の環境で動作確認を行いました：

```bash
$ java -version
openjdk version "25.0.1" 2025-10-21
OpenJDK Runtime Environment GraalVM CE 25.0.1+8.1 (build 25.0.1+8-jvmci-b01)
OpenJDK 64-Bit Server VM GraalVM CE 25.0.1+8.1 (build 25.0.1+8-jvmci-b01, mixed mode, sharing)

$ moon version
moon 0.1.20251205 (073bdea 2025-12-05)
```

### MoonBitのインストール

MoonBitは公式のインストールスクリプトを使用して簡単にインストールできます。

```bash
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
```

インストールが完了すると、`~/.moon/bin`にMoonBitのツールチェーンが配置されます。インストールスクリプトは自動的に`~/.zshrc`にPATHを追加してくれます。

PATHを読み込んで、バージョンを確認します。

```bash
source ~/.zshrc
moon version
```

以下のような出力が表示されればインストール成功です:

```
moon 0.1.20251205 (073bdea 2025-12-05)
```

### プロジェクト構成

今回のサンプルプロジェクトの構成は以下の通りです：

```
.
├── moonbit_tak/
│   ├── moon.mod.json
│   ├── moon.pkg.json
│   ├── moonbit_tak.mbt
│   ├── moonbit_tak_test.mbt
│   └── target/native/release/build/
│       └── libmoonbit_tak.dylib
└── java-moonbit-ffm/
    ├── pom.xml
    └── src/
        ├── main/java/com/example/ffm/
        │   ├── Main.java
        │   ├── TakeuchiFunction.java
        │   └── TakeuchiFunctionJ.java
        └── test/java/com/example/ffm/
            └── TakeuchiFunctionTest.java
```

### MoonBitライブラリの実装

まず、MoonBitで竹内関数を実装します。

#### MoonBitへのログイン

MoonBitでプロジェクトを作成する前に、GitHubアカウントでログインします。これにより、プロジェクト作成時に自動的にユーザー名が設定されます：

```bash
moon login
```

ブラウザが開き、GitHubでの認証が求められます。認証が完了すると、GitHubのユーザー名（例: `making`）がMoonBitのユーザー名として使用されます。

このユーザー名は、後述する関数のシンボル名に含まれます。

#### MoonBitプロジェクトの作成

```bash
moon new moonbit_tak
cd moonbit_tak
```

`moon new`でプロジェクトを作成すると、`moon.mod.json`に自動的にログインしたユーザー名が設定されます：

```json
{
  "name": "making/moonbit_tak",
  ...
}
```

#### 竹内関数の実装

`moonbit_tak.mbt`:

```moonbit
///| Takeuchi function (Tak function)
pub fn tak(x : Int, y : Int, z : Int) -> Int {
  if y < x {
    tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
  } else {
    y
  }
}
```

#### mainコード

`cmd/main/main.mbt`

```moonbit
///|
fn main {
  println(@lib.tak(12, 6, 0))
}
```

mainコードの実行

```bash
moon run cmd/main
```

以下のように出力されれば成功です:

```
12
```

#### テストコード

`moonbit_tak_test.mbt`:

```moonbit
///|
test "tak base cases" {
  // When y >= x, should return y
  inspect(tak(5, 10, 0), content="10")
  inspect(tak(5, 5, 3), content="5")
  inspect(tak(0, 10, 20), content="10")
}

///|
test "tak recursive cases" {
  // Classic test cases for Takeuchi function
  inspect(tak(6, 2, 1), content="6")
  inspect(tak(10, 5, 0), content="10")
  inspect(tak(12, 6, 0), content="12")
}
```

テストを実行

```bash
moon test
```

次のようにテストが成功すればOKです。

```bash
Total tests: 2, passed: 2, failed: 0.
```

#### パッケージ設定

`moon.pkg.json`:

```json
{
  "link": {
    "native": {
      "exports": [
        "tak"
      ],
      "cc-flags": "-fPIC",
      "cc-link-flags": "-shared"
    }
  }
}
```

- `exports`: MoonBitの`tak`関数をエクスポート
- `cc-flags`: Position Independent Code（PIC）フラグで共有ライブラリ用にコンパイル
- `cc-link-flags`: 共有ライブラリとしてリンク

#### ビルド

```bash
moon build --target native
```

`moon build --target native`を実行すると、`moon.pkg.json`の設定に基づいて
`target/native/release/build`以下にビルド結果が生成されます。

```
$ ls -la target/native/release/build
total 512
drwxr-xr-x@ 10 toshiaki  wheel   320B 12 11 12:23 .
drwxr-xr-x@  3 toshiaki  wheel    96B 12 11 12:23 ..
-rw-r--r--@  1 toshiaki  wheel   340B 12 11 12:23 all_pkgs.json
-rw-r--r--@  1 toshiaki  wheel   747B 12 11 12:23 build.moon_db
drwxr-xr-x@  3 toshiaki  wheel    96B 12 11 12:23 cmd
-rw-r--r--@  1 toshiaki  wheel    16K 12 11 12:23 moonbit_tak.c
-rw-r--r--@  1 toshiaki  wheel   796B 12 11 12:23 moonbit_tak.core
-rwxr-xr-x@  1 toshiaki  wheel   176K 12 11 12:23 moonbit_tak.exe
-rw-r--r--@  1 toshiaki  wheel   199B 12 11 12:23 moonbit_tak.mi
-rw-r--r--@  1 toshiaki  wheel    47K 12 11 12:23 runtime.o
```

生成された`moonbit_tak.exe`が共有ライブラリのようです。Mac環境なのに、なぜか`.exe`が生成されますが、
`file`コマンドで確認するとちゃんとMac用の共有ライブラリでした。

```
$ file target/native/release/build/moonbit_tak.exe
target/native/release/build/moonbit_tak.exe: Mach-O 64-bit dynamically linked shared library arm64
```

シンボルの確認：

```bash
nm -g target/native/release/build/moonbit_tak.exe | grep tak
```

出力例：

```
00000000000158b4 T _$making$moonbit_tak$tak
```

`_$making$moonbit_tak$tak`シンボルがエクスポートされていることを確認できます（macOSでは関数名の前に
`_`が付きます）。

この名前は、以下の要素から構成されます：

- `making`: プロジェクトのユーザー名
- `moonbit_tak`: パッケージ名
- `tak`: 関数名

Javaでは`lib<name>.dynlib`形式が期待されているので、シンボリックリンクを作成します。これでいいのか？

```bash
ln -sf $PWD/target/native/release/build/moonbit_tak.exe $PWD/target/native/release/build/libmoonbit_tak.dylib
```

### JavaでのFFM API実装

#### pom.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>java-moonbit-ffm</artifactId>
  <version>1.0.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.source>25</maven.compiler.source>
    <maven.compiler.target>25</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.11.4</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.assertj</groupId>
      <artifactId>assertj-core</artifactId>
      <version>3.27.3</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.0</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.2</version>
        <configuration>
          <argLine>
            -Djava.library.path=${project.basedir}/../moonbit_tak/target/native/release/build
          </argLine>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>
```

#### FFM APIを使用してMoonBitライブラリを呼び出すJavaクラス

`src/main/java/com/example/ffm/TakeuchiFunction.java`:

```java
package com.example.ffm;

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

public class TakeuchiFunction {

  private static final SymbolLookup LIBRARY_LOOKUP;

  private static final MethodHandle TAK_HANDLE;

  static {
    try {
      // Load the MoonBit library
      System.loadLibrary("moonbit_tak");
      LIBRARY_LOOKUP = SymbolLookup.loaderLookup();

      // Create function descriptor for tak(int, int, int) -> int
      FunctionDescriptor takDescriptor = FunctionDescriptor.of(
          ValueLayout.JAVA_INT, // return type
          ValueLayout.JAVA_INT, // x parameter
          ValueLayout.JAVA_INT, // y parameter
          ValueLayout.JAVA_INT  // z parameter
      );

      // Find the MoonBit's mangled tak function directly
      TAK_HANDLE = LIBRARY_LOOKUP.find("$making$moonbit_tak$tak")
          .map(symbol -> Linker.nativeLinker().downcallHandle(symbol, takDescriptor))
          .orElseThrow(() -> new RuntimeException("Failed to find tak function"));
    } catch (Exception e) {
      throw new RuntimeException("Failed to load native library", e);
    }
  }

  public static int tak(int x, int y, int z) {
    try {
      return (int) TAK_HANDLE.invokeExact(x, y, z);
    } catch (Throwable t) {
      throw new RuntimeException("Failed to invoke tak function", t);
    }
  }
}
```

- `System.loadLibrary("moonbit_tak")`: ライブラリ名は`moonbit_tak`（`lib`プレフィックスと`.dylib`
  拡張子は自動的に付加されます）
- `LIBRARY_LOOKUP.find("$making$moonbit_tak$tak")`: エクスポートされた関数名をそのまま使用

FFM APIの主要な概念：

- `System.loadLibrary()`: ネイティブライブラリをロード
- `SymbolLookup`: ライブラリ内のシンボルを検索
- `FunctionDescriptor`: 関数のシグネチャを定義
- `Linker.nativeLinker().downcallHandle()`: ネイティブ関数呼び出し用のメソッドハンドルを作成

#### Java実装（比較用）

`src/main/java/com/example/ffm/TakeuchiFunctionJ.java`:

```java
package com.example.ffm;

public class TakeuchiFunctionJ {

  public static int tak(int x, int y, int z) {
    if (y < x) {
      return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y));
    } else {
      return y;
    }
  }
}
```

### 動作確認用のメインクラス

`src/main/java/com/example/ffm/Main.java`

```java
package com.example.ffm;

import java.util.Scanner;

public class Main {

  public static void main(String[] args) {
    // Check for options
    boolean useJavaImpl = false;
    boolean doWarmup = false;

    for (String arg : args) {
      if ("--java".equals(arg)) {
        useJavaImpl = true;
      }
      if ("--warmup".equals(arg)) {
        doWarmup = true;
      }
    }

    // Perform warmup if requested
    if (doWarmup) {
      performWarmup(useJavaImpl);
    }

    Scanner scanner = new Scanner(System.in);
    System.out.println("Takeuchi Function Calculator");
    System.out.println("Implementation: " + (useJavaImpl ? "Java" : "MoonBit (FFM)"));
    System.out.println("Enter 'quit' or 'q' to exit");

    while (true) {
      System.out.print("\nEnter x y z (space separated): ");
      String input = scanner.nextLine().trim();

      if (input.equalsIgnoreCase("quit") || input.equalsIgnoreCase("q")) {
        System.out.println("Goodbye!");
        break;
      }

      String[] parts = input.split("\\s+");
      if (parts.length != 3) {
        System.out.println("Error: Please enter exactly 3 integers");
        continue;
      }

      try {
        int x = Integer.parseInt(parts[0]);
        int y = Integer.parseInt(parts[1]);
        int z = Integer.parseInt(parts[2]);

        long startTime = System.currentTimeMillis();
        int result = useJavaImpl ? TakeuchiFunctionJ.tak(x, y, z) : TakeuchiFunction.tak(x, y, z);
        long endTime = System.currentTimeMillis();

        System.out.println("tak(" + x + ", " + y + ", " + z + ") = " + result);
        System.out.println("Time: " + (endTime - startTime) + " ms");
      } catch (NumberFormatException e) {
        System.out.println("Error: Please enter valid integers");
      } catch (Exception e) {
        System.out.println("Error: " + e.getMessage());
      }
    }

    scanner.close();
  }

  private static void performWarmup(boolean useJavaImpl) {
    System.out.println("Warming up implementation...");

    if (useJavaImpl) {
      System.out.print("Java warmup: ");
      for (int i = 0; i < 50; i++) {
        TakeuchiFunctionJ.tak(12, 6, 0);
        if ((i + 1) % 10 == 0)
          System.out.print(".");
      }
      System.out.println(" done");
    } else {
      System.out.print("Rust warmup: ");
      for (int i = 0; i < 50; i++) {
        TakeuchiFunction.tak(12, 6, 0);
        if ((i + 1) % 10 == 0)
          System.out.print(".");
      }
      System.out.println(" done");
    }
    System.out.println("Warmup completed!\n");
  }
}
```

#### テストクラス

`src/test/java/com/example/ffm/TakeuchiFunctionTest.java`:

```java
package com.example.ffm;

import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import static org.assertj.core.api.Assertions.assertThat;

class TakeuchiFunctionTest {

  @FunctionalInterface
  interface TakFunction {
    int tak(int x, int y, int z);
  }

  static Stream<TakFunction> takFunctionProvider() {
    return Stream.of(TakeuchiFunction::tak, TakeuchiFunctionJ::tak);
  }

  @ParameterizedTest
  @MethodSource("takFunctionProvider")
  void testBaseCases(TakFunction takFunction) {
    // When y >= x, should return y
    assertThat(takFunction.tak(5, 10, 0)).isEqualTo(10);
    assertThat(takFunction.tak(5, 5, 3)).isEqualTo(5);
    assertThat(takFunction.tak(0, 10, 20)).isEqualTo(10);
  }

  @ParameterizedTest
  @MethodSource("takFunctionProvider")
  void testRecursiveCases(TakFunction takFunction) {
    // Classic test cases for Takeuchi function
    assertThat(takFunction.tak(6, 2, 1)).isEqualTo(6);
    assertThat(takFunction.tak(10, 5, 0)).isEqualTo(10);
    assertThat(takFunction.tak(12, 6, 0)).isEqualTo(12);
  }
}
```

#### テスト実行

```bash
mvn test
```

結果：

```
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
```

MoonBit実装とJava実装の両方で全テストが成功しました！

#### アプリケーション実行

Rust FFM実装の実行：

```bash
java -cp target/classes --enable-native-access=ALL-UNNAMED -Djava.library.path=../moonbit_tak/target/native/release/build com.example.ffm.Main
```

Java実装の実行（比較用）：

```bash
java -cp target/classes com.example.ffm.Main --java
```

実行結果例

```
Takeuchi Function Calculator
Implementation: MoonBit (FFM)
Enter 'quit' or 'q' to exit

Enter x y z (space separated): 12 6 0
tak(12, 6, 0) = 12
Time: 68 ms

Enter x y z (space separated): 10 5 0
tak(10, 5, 0) = 10
Time: 2 ms
```

### パフォーマンス比較

`tak(12, 6, 0)`、`tak(14, 7, 0)`、`tak(15, 5, 0)`、`tak(15, 7, 0)`の実行をRust FFM実装とJava実装で比較してみました。

Rust FFM実装：

```bash
echo -e "12 6 0\n14 7 0\n15 5 0\n15 7 0\nq" | java -cp target/classes --enable-native-access=ALL-UNNAMED -Djava.library.path=../moonbit_tak/target/native/release/build  com.example.ffm.Main --warmup
```

Java実装：

```bash
echo -e "12 6 0\n14 7 0\n15 5 0\n15 7 0\nq" | java -cp target/classes com.example.ffm.Main --java --warmup
```

結果は次の通りでした。Rustで試した場合はRust FFM実装の方がJava実装に比べて約1.4倍高速でしたが、MoonBit
FFM実装の場合は、Java実装の約0.5倍の速度になりました。
竹内関数が再帰が多いので、その辺りのパフォーマンス的にはまだ発展途上でしょうか。

| Test Case       | MoonBit FFM (ms) | Java (ms) | MoonBit優位率 | 差分 (ms) |
|-----------------|------------------|-----------|------------|---------|
| `tak(12, 6, 0)` | 21               | 10        | 0.47x      | 11      |
| `tak(14, 7, 0)` | 974              | 497       | 0.51x      | 477     |
| `tak(15, 5, 0)` | 4,852            | 2,456     | 0.50x      | 2396    |
| `tak(15, 7, 0)` | 6,670            | 3,375     | 0.50x      | 3295    |


fibonacci関数のサンプルコードは次のように、`loop`構文で末尾再帰最適化をしているように見えます。

```moonbit
pub fn fib(n : Int) -> Int64 {
  loop (n, 0L, 1L) {
    (0, _, b) => b
    (i, a, b) => continue (i - 1, b, a + b)
  }
}
```

竹内関数は非末尾再帰なので`loop`構文では書けず、最適化ができていないのかもしれません。

`fib`関数の`loop`構文有無とRust FFM実装、Java実装のパフォーマンス比較も試してみたいところです。

---

この記事では、MoonBitで実装した竹内関数をJavaのFFM APIから呼び出す方法を紹介しました。

MoonBitは現在ベータ版で、2026年前半に正式版1.0のリリースが予定されています。今後、より多くの言語との連携が期待されます。

今回の記事はMoonBitの例としてはあまり適切ではないかもしれません。もっと別のユースケースでMoonBitを試したいと思います。

参考:

- [MoonBit公式サイト](https://www.moonbitlang.com/)
- [MoonBit FFI ドキュメント](https://docs.moonbitlang.com/en/latest/language/ffi.html)
- [MoonBit Native Backend](https://www.moonbitlang.com/blog/native)
