enchanのメモ書き

計算機とフリルとラブライブ!

macOS Ventura 13.6.4でLua5.4.6をビルド

色々と調査した結果ビルド芸関連の記事が頻繁にアクセスされていることがわかったので、久々にやっていきたいと思います。
ビルド芸は結構色んな知識が得られるので嫌いじゃないんですが、いかんせん変なところで詰まったり結局それができて何が嬉しいんだみたいなことになったりしがちなので、 今回は次につながるステップの一つとしてあの言語をビルドしていきたいと思います。まあタイトルでバレてるんですけども。

Lua

概要

プログラミング言語「Lua」。高い移植性と組込みの容易さから、様々なソフトウェアのプラグインとして採用されているスクリプト言語です。 名前の由来はポルトガル語の「月」からきているんだとか。なんかちょっとおしゃれです。

The programming language Lua - lua.orgより引用

個人的には、AviUtlやnginx、そのほかAdobe製のソフトウェアで見かける印象が強いです。ゲーム制作にはあまり明るくないのですが、イベントスクリプトを記述するための言語として広く採用されていたりする(The Engine Survey: General results - SATORI)んだとか。

コードはLua VMと呼ばれる環境上で実行されます。ソースをそのまま食わせることも、独自のバイトコードに"コンパイル" することも可能です。
なので、種類としてはインタプリタ型に属する…のかな? ただ The LuaJIT Project なるものが存在するらしいです。この辺りは詳しい方にお任せします。

記法

基本的な記法はこんな感じ。 FizzBuzzは要件がシンプルなだけに何も考えないで書くと変なミスしますね。おおこわいこわい。

--
-- FizzBuzz
--

-- 最大値を取得
io.write("Limit: ")
local limit = tonumber(io.read())

--カウントアップ
for n = 1, limit do
    if n % 3 == 0 and n % 5 == 0 then
        print("FizzBuzz")
    elseif n % 3 == 0 then
        print("Fizz")
    elseif n % 5 == 0 then
        print("Buzz")
    else
        print(n)
    end
end

Pythonっぽいなーとか思ってましたが、Wikipediaさん曰くPascalに類似しているとのこと。
Pascalは未履修なのでよくわかりません、すみません…

本題

別に言語紹介は今回の本題ではないので、早速ビルド芸の方を始めたいと思います。
先述の通りLuaはソフトウェアへの組込みを想定して設計されているため、ビルド方法は公式で詳しく解説されています。

Lua: download

正直これが全てと言っても過言ではないのですが、まあ、はい…

 

 

やっていきましょう。 ソースを落として展開します。

% wget https://www.lua.org/ftp/lua-5.4.6.tar.gz
% tar xzf lua-5.4.6.tar.gz

tree するとわかるのですが、めちゃくちゃ軽いです。ソース以外の全てのファイルを含めても100ファイルを切ります。
数字としてイメージしづらいと思うので、過去にビルドしてきたものたちと比較してみました。組込み前提とはいえ、この軽さは凄まじい。

…冷静に考えるとこのグラフは一体なんなんでしょうか。

 

なにはともあれ、ソースの用意はできました。早速ビルドしましょう。

% make

…ビルド芸につきものなのが、このmakeの完了を待つ時間。まあ、ゆっくりいきましょう。私は今推しを抱えてこの記事を書いています。

Build with You!! - 虹ヶ咲学園スクールアイドル同好会

せっかくですしアニメでも観ましょうか。先日発表された蓮ノ空とアイカツのコラボの話を友人に投げたところ謎の交換条約が成立し、友人はアニガサキを、私は『響け!ユーフォニアム』を履修することになりました。

うん、そうしましょう。せつ菜ちゃんもその方が退屈しないと思います(まあ一応IT活用チームの一員ではありますが)。

…おや? ターミナルが止まっています。なんでしょう。またbrewが古いとか言い出したんでしょうか。

…ん?

% echo $?
0

なんとビルドが終了していました
速い、速すぎる。なんだお前。

流石に気になったので計測してみました。

% make clean
% time make
(省略)
make  6.14s user 1.83s system 93% cpu 8.492 total
%

なんと10秒を切っています。速っや。これではアニメどころではありません。
make test に至っては一瞬で終わってしまいました。あ、そう…

 

ビルドが完了したら次はインストールです。make install するとシステムに直接インストールされますが、make local するとソースファイル直下に install ディレクトリが生成され、そこにバイナリが展開されます。 今回はシステムでも使いたいので、両方実行しました。

試してみる

早速試してみましょう。先ほどのFizzBuzzのソースを用意し(main.lua)、コンパイルしてみます。

% luac -o main.out main.lua

luac によって生成されるバイトコードは Lua bytecode と呼ばれる独自形式のようです。

% file main.out 
main.out: Lua bytecode, version 5.4

実行します。

% lua main.out 
Limit: 20
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

いい感じにFizzBuzzできました。

他言語に組み込んでみる

先述の通り、Luaは既存のソフトウェアへ容易に組み込むことが可能です。こちらも軽く試してみましょう。今回はこちらのページを参考に実装しています。

densan-labs.net

はじめに、ファイル test.lua を用意しました。

--
-- call Lua from C
--

io.write("Hello! I'm Lua.\n")

local n = 10
print("The factorial of " .. n .. " is " .. factorial(n) .. ".")

function sum(limit)
    local total = 0
    for i = 1, limit do
        total = total + i
    end
    return total
end

関数 factorial はLuaの標準関数には存在しないので、このコードはこのままでは実行できません。

次に、このようなCソースを準備します。

クリックして展開

//
//
//
#include <lua/lauxlib.h>
#include <lua/lua.h>
#include <lua/lualib.h>
#include <stdio.h>

const char* fileName = "test.lua";

// 階乗
int l_factorial(lua_State*);

int main(int argc, char const* argv[]) {
    // Luaの状態を新しく生成し、保持
    lua_State* Lua = luaL_newstate();

    // 標準ライブラリの関数を使えるようにする
    luaL_openlibs(Lua);

    // Cの関数をLuaに登録
    lua_register(Lua, "factorial", l_factorial);

    /* ---------- */

    // ソースファイルを読み込む
    int err = luaL_loadfile(Lua, fileName);
    if (err != 0) {
        printf("An error occured while loading source file: %s\n", lua_tostring(Lua, -1));
        return err;
    }

    // 読み込んだLuaソースを実行する ルートに記述されたステートメントはここで実行される
    err = lua_pcall(Lua, 0, 0, 0);
    if (err != 0) {
        printf("An error occured while running script: %s\n", lua_tostring(Lua, -1));
        return err;
    }

    /* ---------- */

    // 0. Luaで定義した関数を呼び出す 関数名と引数を用意
    const char* funcName = "sum";
    int param = 10;

    // 1. グローバルスコープで定義された関数オブジェクトを名前から引き、"Luaスタック" に積む
    lua_getglobal(Lua, funcName);

    // 2. 引数値を積む
    lua_pushnumber(Lua, param);

    // 3. pcallを呼び出して実行する 引数は(状態, 引数の数, 戻り値の数, エラー格納先)
    err = lua_pcall(Lua, 1, 1, 0);
    if (err != 0) {
        printf("An error occured while calling function: %s\n", lua_tostring(Lua, -1));
        return err;
    }

    // 4. 値を確認して取り出す
    if (!lua_isnumber(Lua, -1)) {
        printf("Unexpected return value type: %s\n", lua_typename(Lua, lua_type(Lua, -1)));
        return 1;
    }
    int result = lua_tonumber(Lua, -1);
    printf("Function invocation result: %s(%d) = %d\n", funcName, param, result);

    // 5. スタックからポップ(して値を破棄)する
    lua_pop(Lua, 1);

    // 状態を終了し、閉じる
    lua_close(Lua);
    return 0;
}

int l_factorial(lua_State* Lua) {
    // Luaスタックから引数値を取得
    int n = luaL_checkinteger(Lua, -1);

    // 計算
    int total = n;
    for (int i = n - 1; i > 0; i--) {
        total *= i;
    }

    // 計算結果を積み、「関数の戻り値の数」を返す
    lua_pushinteger(Lua, total);
    return 1;
}

ここではLuaを準備し、関数l_factorialfactorial という名前で登録しています。これにより、Luaスクリプト側から関数 factorial を呼ぶことができるようになります。
次にソースファイル test.lua を開き、スクリプトとして読み込みます。このタイミングで test.lua が実行され、メッセージの表示および関数 sum の定義が行われます。
最後に関数 sum をCから呼び出し、結果を出力します。

コンパイル時は lua をリンクしましょう。

% gcc main.c -llua

実行するとこんな感じ。

% ./a.out 
Hello! I'm Lua.
The factorial of 10 is 3628800.
Function invocation result: sum(10) = 55

Luaで記述した関数をCから、Cで記述した関数をLuaからそれぞれ呼び出すことができています。
実に簡単です。

うまくいきすぎてつまんない!

……特に何の問題も起こらないので面白みがありませんね。
これではビルド芸ではなくただのビルド日記です。
もっとこう、心躍る展開が欲しい。

Swiftから呼び出す

ということでSwiftから呼び出してみましょう

ここから先は長くなるというか普通に有用な気がしてきたので、Zennにちゃんとした記事として投稿したいと思います。投稿次第こちらにもリンクする予定です。
実装としては完了している(v0.1.0リリース済)ので、一応repoのURLを貼っておきます。

github.com

というわけで、基本的なビルド方法とCとの連携例は以上です。お疲れ様でした。

追記 (20240318): やっとZennが生えました。

zenn.dev

fflush(stdout)