フォートシステムを実装するには、いくつのプリミティブが必要ですか?

1992年には、C言語での難読化されたプログラミングのための別の コンテストが開催されました。提示されたプロジェクトの1つは、小さな 砦システムでした。仮想マシンがわずか794バイトのCコードで実装されていることに気づきました。フォートシステムの残りの部分は、フォートのソースからロードされました。プロジェクトを研究した後、著者は完全に「正直な」トリックではなく、scanf()関数を使用して強化されたソースを解析したため、最初の興奮は失望に変わりました。その瞬間から、私は質問に苦しめられました-そのようなトリックなしで砦システムを実装するためにいくつのプリミティブが必要ですか?



しばらくして、私はに導入されたsod32砦システム によって書かれた レナートBenschop。このフォートシステムの場合、仮想マシンはCで記述され、仮想マシンにロードされるバイナリイメージは、ワードホストシステムのバイト順序に依存しません。 sod32には32個のプリミティブがあり、次のとおりです。

NOOP ( --- ) Do nothing
SWAP ( x1 x2 --- x2 x1 ) Swap the two top items on the stack.
ROT ( x1 x2 x3 --- x2 x3 x1 ) Rotate the three top items on the stack.
0= ( x --- f) f is true if and only if x is 0.
NEGATE ( n1 --- -n1) Negate top number on the stack.
UM* ( u1 u2 --- ud ) Multiply two unsigned numbers, giving double result.
C@ ( c-addr --- c) Fetch character c at c-addr.
@ ( a-addr --- x) Fetch cell x at a-addr.
+ ( w1 w2 --- w3) Add the top two numbers on the stack.
AND ( x1 x2 --- x3) Bitwise and of the top two cells on the stack.
OR ( x1 x2 --- x3) Bitwise or of the top two cells on the stack.
XOR ( x1 x2 --- x3) Bitwise exclusive or of the top two cells on the stack.
U< ( u1 u2 ---- f) f is true if and only if unsigned number u1 is less than u2.
< ( n1 n2 --- f) f is true if and only if signed number n1 is less than n2.
LSHIFT ( x1 u --- x2) Shift x1 left by u bits, zeros are added to the right.
RSHIFT ( x1 u --- x2) Shift x1 right by u bits, zeros are added to the left.
UM/MOD ( ud u1 --- urem uquot) Divide the unsigned double number ud by u1, giving unsigned quotient and remainder.
+CY ( n1 n2 cy1 --- n3 cy2) Add n1 and n2 and the carry flag cy1 (must be 0 or 1) giving sum n3 and carry flag cy2. (n3 cy2 can be seen as a double number)
SCAN1 ( x d --- u) Scan x for the first 1 bit. u is the position of that bit (counted from the scan direction) and 32 if x=0. d is the scan direction, 0 is left-to-right, 1 is right-to-left.
SPECIAL ( x ---) Any of a large number of special instructions, indicated by x.
DROP ( x ---) Discard the top item on the stack.
>R ( x ---) Push x on the return stack.
C!A ( c c-addr --- c-addr) Store character c at c-addr, keep address.
!A ( x a-addr --- a-addr) Store cell x at a-addr, keep address.
DUP ( x --- x x ) Duplicate the top cell on the stack.
OVER ( x1 x2 --- x1 x2 x1) Copy the second cell of the stack.
R@ ( --- x) x is a copy of the top of the return stack.
R> ( --- x) Pop the top of the return stack and place it on the stack.
0 ( --- 0) The constant number 0.
1 ( --- 1) The constant number 1.
4 ( --- 4) The constant number 4.
LIT ( --- lit) Push literal on the stack (literal number is in-line).






私は自分の質問に対する答えを見つける機会があることに気づき、プリミティブを高レベルの定義に変え始めました。このすべての活動は純粋に学術的な意味を持っていることをすぐに指摘したいと思います。性能が低下するため、実際に得られた結果を適用できる可能性は低いです。実験しながら、フォートシステムバイナリのサイズとジョンヘイズテストスイートのランタイムを追跡しました。コマンドを使用して新しいバイナリイメージを作成しました

echo 'S" extend-cross.4" INCLUDED '|./sod32 kernel.img
      
      





次のようなテストを実行しました。

time ./sod32 kernel.img <<< $(echo -e 'S" tester.fr" INCLUDED\n12345\nBYE')
      
      





次の表で、各変更がサイズとパフォーマンスにどのように影響したかを確認できます。「変更」列からのリンクは、githubの対応するコミットにつながります。

変更 kernel.imgサイズ 実行時間tester.fr
オリジナルのsod32 10164 0分0.059秒
lshift、rshift 10312 0分0.071秒
+、um *、um / mod 11552 0分0.123秒
c @、c!a 11952 0分0.795秒
0 =、否定<、u < 12340 0m2.800s
落とす 12784 0分5.022秒
スワップ、腐敗、オーバー 13436 0分5.860秒
sp @、sp!、rp @、rp!、dup 13680 0分8.696秒
r @、r>、> r 14160 0分15.463秒
および、または、xor 14336 0分21秒198秒
0、1、4 15236 0分21.671秒
0ブランチ 15912 0分41秒765秒


その結果、フォートシステムのバイナリイメージのサイズが10164から15912(+ 57%)に増加し、パフォーマンスが708倍(ほぼ3桁)低下しました。コードのプロファイリングとボトルネックの最適化によってパフォーマンスを改善できる可能性がありますが、元のsod32と比較して結果は依然として非常に遅いと確信しています。



32個のプリミティブと内部インタープリターの追加ロジックから、7個になりました:nop、nand、!、@、Um +、special、lit。内部インタープリターは、プリミティブと高レベルの定義を実行するためのロジック(呼び出し)、および高レベルの定義を完了するためのロジック(次へ)を保持します。私は私の質問に対する答えを見つけました:フォートシステムは9つのプリミティブ(またはnopがプリミティブである必要がない場合は8つ)に基づいて構築できます。メモリアクセス用のプリミティブが3つあるという事実に混乱しています:@、!と点灯しましたが、それを回避する方法がわかりませんでした。私は何かを見逃した可能性があるので、多数のプリミティブを取り除く方法を知っている場合は、コメントに書き込んでください。



All Articles