一人一党党

一人一人の、一人一人による、一人一人のための政治制度を!

スタックマシンで文明崩壊後のセルフアセンブラ - 変数の実装

これは 言語実装 - Qiita Advent Calendar 2025 - Qiita の18日目の記事です。

去年、2024年のAdvent Calendar で手っ取り早く挑戦した 「スタックマシンで文明崩壊後のセルフアセンブラ 」 で、「人間コンパイラ」に丸投げしていた変数の実装ですが。 これを支援する機能を追加しました。 成果物は去年と同じ場所 に置きます。

マクロでニーモニック実装、再び

仕組みとしては、スタック増減を示す注釈を命令毎に付記することで、 スタックトップからの変数の相対位置をアセンブラに計算させます。 元ネタのstage0と同様に、 stage0riscでもニーモニックの実装には 機械語ビット列が実体であるマクロを使っています。

# head.asm
# (このような行指向コメント機能は実装していないことに注意)
# {実体}ニーモニック
{00001 000b}rel         # 相対アドレスから絶対アドレスを計算する
{00010 000b}jmpabs      # 絶対アドレスで表された先へ分岐
{00011 000b}callabs     # 呼び出し元アドレスを残す
{00100 000b}beqzabs     # 追加のフラグ引数が0なら分岐

ほとんどの命令はスタックの増減量が決まっているので、 これらのニーモニックのマクロに、スタック増減注釈を付記します。

# head-rsh.asm
{00001 000b    }rel     # 相対アドレスを消費して絶対アドレスを残すから±0
{00010 000b  1-}jmpabs  # アドレスを消費
{00011 000b  1-}callabs # 呼び出し元アドレスは呼び出し先で消費される
{00100 000b 10-}beqzabs # アドレスとフラグを消費(二進数で表記)

このニーモニック定義マクロを用いることで、 これが冒頭に追加される各アセンブリコードでは スタック増減注釈の付記がほぼ不要になります。

スタック増減の処理は バイナリ生成パス「numlabel」を機能拡張する形で実装しました。 UNIXでの「パイプライン処理」やマルチパスコンパイラと同様に、 stage0riscアセンブラは複数の小さなプログラム「パス」で構成されており、 前のパスの出力を次のパスが入力として受け取って少しずつ変換することにより、 ソースコードからバイナリイメージを生成します。 numlabelパスには数値を扱う機能が元々実装されているので、 これを使いまわすことで、追加するコード量を少なく済まそうと思います。 とはいえ、新機能を用いたアセンブラでnumlabelパスのソースコードを書いても、 それを扱うnumlabelバイナリは未生成という ブートストラップ問題 に遭遇します。 同じ問題は去年にも遭遇しており、 その解決策として去年に倣い、C言語でnumlabelパスを用意しました。

実際に使ってみた感想

セルフアセンブラを名乗る以上、 自身のバイナリを生成するために実際に使わざるを得ません。

まずは、この新機能に私自身が習熟するために、 去年のバイナリイメージを出力すべく 今回のアセンブラソースコードを書き換えてみました。 すると、スタックマシンではスタックトップを引数として暗黙のうちに使うため、 変数を使う機会を少なく感じました。 今回の機能追加、あまり意味なかったか?

しかし、ブートストラップ問題回避用のC言語版から乗り換えるべく作成した、 numlabelパスの当スタックマシン用バイナリイメージを 去年のものと比較してみたところ、 スタック増減を記録するために追加した変数のために、 広い範囲に散らばったビット化けがみられました。 これを去年のアセンブラデバッグするのは大変だったはずだと思います。