以下では仕様について書いていきます。雑記・考察ログを見たい人は「オペコードなどについて考察する」をお読みください。専門用語の一覧は用語集に掲載されています。インタプリタで実装されている追加仕様などについてはインタプリタの追加設定一覧(暫定)を参照ください。Twitterで出た質問はTogetterにまとめてあります(part1, part2, part3)。ソースコードの例としては非再帰フィボナッチと再帰フィボナッチとquicksortと開平があります。
pFftcxkqhRzmnrljwbVvdsgXiyuoea0123456789'-_
である。ただし、既存のレジスタ名として利用されている名前や、全て数字のみで構成されているものは、有効なラベル名として用いることができない。+
と@
は前後に空白が入っていようがいまいが解釈は変わらない。ただし、コーディング規約としては+
の前後には空白を入れず、@
の前には空白を入れないのが好ましい。細かい仕様が確定しておらずインタプリタでまだ実行できない命令についてはフォントサイズを小さくしている。
命令 | 語源 | x86 | 意味 |
krz または kRz | kRantairzarth | mov | 複製(第1オペランドの値を第2オペランドに代入。'c'i指定があるときは第2オペランドと第1オペランドの役割が逆転する。) |
malkrz または malkRz | mal kRantairzarth | cmovcc | フラグが立っているときのみkrzを行う |
fen | fav es niv e'i | nop |
何もしない。krz f0 f0と等価。オペランドを取らない。 |
inj | irzarst ileceonj | 二重移動 (第1オペランドの値を第2オペランドに代入しつつ第2オペランドの古い値を第3オペランドに代入。'c'i指定があるときは第3オペランドと第1オペランドの役割が逆転する。) |
2018年8月27日追記:なお、厳密には、inj
の挙動を
という操作をこの順に行う行為と同等であるべし、と定義する。
早見表 (以下a, b, cはそれぞれ異なる場所を指すとする): inj a b c : cには旧bが、bには旧aが入る。aは変更されない。 inj a b a : aとbの値が交換される。 inj a a c : cには旧aが入る。aは変更されない。 inj a b b : どの値も変更されない。 inj a a a : どの値も変更されない。
2018年8月27日追記:なお、inj
のさらなる規定として、第二オペランドにレジスタ、第三オペランドにメモリを取る場合で、第三オペランドが第二オペランドで登場するレジスタを使っていた場合の動作を未定義(何が起きても構わない)とする。
基本的にオペランドを2つ取り、第二オペランド(書き込み可能である必要がある)に対して第一オペランドの値を用いた算術演算を行う。例えば、nta 4 f5
であれば、「レジスタf5の値を読み取り、そこから4を引いた値をレジスタf5に書き込む」という意味である。'c'i
擬似命令がある場合は第一オペランドと第二オペランドの役割が入れ替わる。
nac
は0をdalすることと等価であり、オペランドを1つのみ取る。
lat
はオペランドを3つ取り、以下の動作をする。
'i'c
の時:第二オペランド(書き込み可能である必要がある)に対して第一オペランドの値を用いた乗算を行い、上位32ビットを第三オペランド(書き込み可能である必要がある)に書き込んだ後に下位32ビットを第二オペランドに書き込む。'c'i
の時:第一オペランド(書き込み可能である必要がある)に対して第三オペランドの値を用いた乗算を行い、上位32ビットを第二オペランド(書き込み可能である必要がある)に書き込んだ後に下位32ビットを第一オペランドに書き込む。2018年8月27日追記:なお、lat
のさらなる規定として('i'c
の時)、第三オペランドにレジスタ、第二オペランドにメモリを取る場合で、第二オペランドが第三オペランドで登場するレジスタを使っていた場合の動作を未定義(何が起きても構わない)とする。
命令 | 語源 | x86 | 意味 |
ata | atakeses | add | 加算 |
nta | ny atakeses | sub | 減算 |
lat | latvaves | (mul) | 乗算(符号なし) |
latsna | latvaves (snakxazasyk) | (mul) | 乗算(符号付き) |
(kak) | (kakites) | (div) | 除算 |
ada | adales | and | ビット積 |
ekc | ekcan | or | ビット和 |
nac | nacises | not | ビット反転。0をdalすることと等価。 |
dal | daliuales | xnor | ビットxnor |
dto | dusnij tesnokonj | shr | 論理右シフト |
dro または dRo | dusnij Restutonj | shl | 左シフト |
dtosna | dusnij tesnokonj (snakxazasyk) | sar | 算術右シフト |
2018年8月27日追記:シフト命令でシフトするビット数は0~63ビットまでは合法である。64ビット以上は未定義とする。
@sosoBOTpi 2003lkのdto, dro, dtosnaで32ビット以上シフトしたときって0とかになるんでしょうか。シフト量の値をmod 32して扱ったり、32以上のシフトを未定義にしたりする処理系が多いみたいですし、実際論理回路で組むのが面倒なので
— 炭酸ソーダ (@na2co3_ftw) 2017年12月15日
実装依存にしてしまっていい気もしますが、ぴったり32ビットのシフトって意外と使う気がしないでもないので、どうしたもんかなぁ、とは思っています
— .sozysozbot.@hsjoihs@jekto.vatimeliju (@sosoBOTpi) 2017年12月15日
なるほど。32には対応して、33以上は未定義にするのであれば回路もそんなに複雑にはならないですね。(x & 32のビットだけ読んで判断できるので)
— 炭酸ソーダ (@na2co3_ftw) 2017年12月15日
となると、「32~63はゼロ、64以上は未定義」とできますね。現世と違って最初は寡占でしたし、最初に採用された仕様がずっと引き継がれても違和感もそんなにありません
— .sozysozbot.@hsjoihs@jekto.vatimeliju (@sosoBOTpi) 2017年12月15日
ですねえ(現世での実装が面倒)。あとdtosnaで元の数がマイナスだった場合は-1になりますかね。
— 炭酸ソーダ (@na2co3_ftw) 2017年12月15日
ですね。(ちなみにHaskellで書いているのでビットシフトは合理的に動きます、Cの遺産を引きずっていないので)
— .sozysozbot.@hsjoihs@jekto.vatimeliju (@sosoBOTpi) 2017年12月15日
fi
の後に値を二つ取り、その二つの大小に応じてフラグを設定する命令。'c'i
擬似命令や'i'c
擬似命令の影響を受けない。
命令 | 語源 | 意味 |
fi ~ xtlo | fi * es * xut loler | 以下ならフラグを立てる(符号付き比較) |
fi ~ xylo | fi * es * xy loler | 未満ならフラグを立てる(符号付き比較) |
fi ~ clo | fi * es * ce loler | 同等ならフラグを立てる |
fi ~ xolo | fi * es * xo loler | 以上ならフラグを立てる(符号付き比較) |
fi ~ llo | fi * es * le loler | 超過ならフラグを立てる(符号付き比較) |
fi ~ niv | fi * es * niv | 等しくないならフラグを立てる |
fi ~ xtlonys | fi * es * xut loler (ny snakxaz) | 以下ならフラグを立てる(符号無し比較) |
fi ~ xylonys | fi * es * xy loler (ny snakxaz) | 未満ならフラグを立てる(符号無し比較) |
fi ~ xolonys | fi * es * xo loler (ny snakxaz) | 以上ならフラグを立てる(符号無し比較) |
fi ~ llonys | fi * es * le loler (ny snakxaz) | 超過ならフラグを立てる(符号無し比較) |
命令 | 意味 | 精密な定義 |
krz8i または kRz8i | -'iが8bitの複製(8bitロード) | 第1オペランドの指す32bitの値のうち、上位8bitを取って符号拡張して32bitにし、それを第2オペランドに代入。'c'i指定があるときは第2オペランドと第1オペランドの役割が逆転する。 |
krz8c または kRz8c | -'cが8bitの複製(8bitストア) | 第1オペランドの値の下位8bitを、第2オペランドの指す32bitの内上位8bitに代入。'c'i指定があるときは第2オペランドと第1オペランドの役割が逆転する。 |
krz16i または kRz16i | -'iが16bitの複製(16bitロード) | 第1オペランドの指す32bitの値のうち、上位16bitを取って符号拡張して32bitにし、それを第2オペランドに代入。'c'i指定があるときは第2オペランドと第1オペランドの役割が逆転する。 |
krz16c または kRz16c | -'cが16bitの複製(16bitストア) | 第1オペランドの値の下位16bitを、第2オペランドの指す32bitの内上位16bitに代入。'c'i指定があるときは第2オペランドと第1オペランドの役割が逆転する。 |
注
レジスタはリパライン語でfirjalである。
レジスタ名 | 役割 |
f0 | 関数内でいじって良いレジスタ。単純型戻り値はf0で返す。 |
f1 | |
f2 | |
f3 | |
f4 | 未設定 |
f5 | 主にスタックを扱うためのレジスタ。 |
f6 | 未設定 |
xx | xeumon xelal「次に見るところ」の略。次に実行する命令のアドレスが格納されている。CPUは |
構文 | 語源 | 役割 |
'c'i | 第一オペランドが-'c格、第二オペランドが-'i格として解釈される。現世の表現で言うと、GNU as的 mov src, dest の語順でなくMASM的 mov dest, src の語順になる。ただし、比較演算子はひっくり返らない。 | |
'i'c | 第一オペランドが-'i格、第二オペランドが-'c格として解釈される(これがデフォルトの語順である)ように設定する。現世の表現で言うと、MASM的 mov dest, src の語順でなくGNU as的 mov src, dest の語順になる。ただし、比較演算子はひっくり返らない。 | |
レジスタ名@ | レジスタに入っている番地のメモリを表す。 | |
レジスタ名 + 定数@ | レジスタに入っている番地に定数を足した番地のメモリを表す。 | |
レジスタ名 + レジスタ名@ | 2つのレジスタに入っている数値を足した番地のメモリを表す。 | |
l' ラベル名(後ろから修飾) | lex | 直前の命令に対してラベル名を定義する |
nll ラベル名(前から修飾) | ny la lex | 直後の命令に対してラベル名を定義する |
kue ラベル名 | kinunsares | ラベル名を他のファイルからも見えるようにする |
xok ラベル名 | xokison | 他のファイルで定義されたラベル名を見えるようにする |
+
がファイル末尾に来たり、+
や@
がファイルの先頭に来たりするのは構文エラーである。l'
ラベル名の直前には命令文がなくてはならず、また直前にnll
があってはならない。一つの命令文の後ろに複数のl'
を置くのは許容され、その場合両方のラベルが命令文を指す。nll
ラベル名の直後には命令文がなくてはならず、また直後にl'
があってはならない。一つの命令文の前に複数のnll
を置くのは許容され、その場合両方のラベルが命令文を指す。'c'i
や'i'c
はアセンブラにオペランド順を指示するだけのディレクティブである。ソースコード上に複数書くことが可能であり、適応範囲は「そこから下、終わりまでか、次にまたオペランド順が指定されるまで」である。以下の炭酸ソーダさんの解釈が正しい。
2003lkで気になることが。
— Ritchan(りっちゃん) (@aios_ciao) 2018年3月1日
'i'c krz 12 f0 krz 34 f1 krz 0 f2
nll lbl ata f0 f1
'c'i nac f2 ada f2 1
fi f2 0 niv malkrz xx lbl
'i'c krz lbl xx
こんなループの場合、ataの挙動ってどうなりますかね。
見落としてたらすみません。
簡単に言えば、'c'iや'i'cってソースにひとつだけ書けるのか、複数書けるのか。
— Ritchan(りっちゃん) (@aios_ciao) 2018年3月1日
複数書けるなら適用範囲はどうなるのか。
そこが解ってません。
複数書けます。適応範囲は、ソースコード上で、そこから下、終わりまでか、次にまたオペランド順が指定されるまでです。
— 炭酸ソーダ (@na2co3_ftw) 2018年3月1日
例の場合だとataは必ず'i'c順ですね
— 炭酸ソーダ (@na2co3_ftw) 2018年3月1日
わざとf2で'c'iと'i'cを交互に通るように書いてみました。
— Ritchan(りっちゃん) (@aios_ciao) 2018年3月1日
パラメータの順序はステートメントを初回実行する時に確定するような振る舞いなんですね。
アセンブラにオペランド順を指示するだけのディレクティブと考えた方が分かりやすいかなと思います。
— 炭酸ソーダ (@na2co3_ftw) 2018年3月1日
その通りです。(たしかにそう明示的に書いておくべきだよなぁ)
— .sozysozbot.@hsjoihs@jekto.vatimeliju (@sosoBOTpi) 2018年5月10日
kue
が無いファイルを先頭から実行する。つまり、kue
が無いファイルはただ1つ存在する必要がある。スタックにiskaをプッシュする
'c'i nta f5 4 ; f5を持ち上げて krz f5@ iska ; 積む
スタックをポップしてiskaに代入
'c'i krz iska f5@ ; スタックの値を代入 ata f5 4 ; f5を下げる
なお、スタックの伸長方向はアドレスの減少する向きである。
returnする
'c'i krz xx f5@ ; リターンアドレスに飛ぶ ; スタックからリターンアドレスを削除するのは呼び出し側の仕事
関数funcを呼び出す
'c'i nta f5 4 ; スタックを準備 inj f5@ xx func ; リターンアドレスを積んで、同一命令で関数のアドレスにジャンプ ata f5 4 ; スタックからリターンアドレスを削除する
aとbを交換
'c'i inj a b a
フラグをレジスタに取り出す(xxレジスタ以外)
'c'i krz f0 0 fi * * * malkrz f0 1
f0を8bitの値と見て32bitに符号拡張する
'i'c dro 24 f0 krz8i f0 f0
左シフトして右シフトすれば符号拡張も0拡張もできるが、この方法だと即値を1つ削減できる。
f0を16bitの値と見て32bitに符号拡張する
'i'c dro 16 f0 krz16i f0 f0
16bitと16bitを連結して32bitに (下位:f0, 上位:f1, 結果:f0)
'i'c krz16c f1 f0
f0が0でなかったら無限ループに突入
'c'i nll jiesesn fi f0 0 niv malkRz xx jiesesn