
過去
物語は1962年にケンブリッジ大学で
CPL (「ケンブリッジプログラミング言語」)-ALGOL-60の「改良版」で作業が開始されたときに開始できます。 大学院生のマーティン・リチャーズがこの言語の研究に参加しました。 新しいYPを実装する際の主な困難は、さまざまなコンピュータープラットフォーム用にコンパイラーを手動で移植する必要があるように思われました。 特に、Cambridge
EDSAC-2が Atlas -2に置き換えられたとき、CPL開発者はコンパイラを新しいプラットフォームに移植するのに多くの時間を費やしました。
Martinの論文は「自己コンパイル」CPL専用でした。Martinによって開発されたコンパイラは、CPLの非常に単純化されたバージョンで記述され、そのコンパイラは当時のマクロアセンブラで簡単に記述できました。 CPLを新しいプラットフォームに移植するには、次の2つの手順を実行できます。
- 「簡易CPL」コンパイラを手動で記述します。
- 「フルCPL」のコンパイラをコンパイルします。
Martinはそこで止まらず、ポータブルコンパイラを開発するためのシステムである
BCPLを開発しました。 BCPLコンパイラは、Martin
"OCODE"と呼ばれる
擬似コードを生成しました。
OCODEは次のようになりました。OCODE | 「デコード」(「プロコード」) | |
---|
94 5 L1 83 73 69 86 69
95 4
42 0
42 0 40 2 14
83
42 0 42 1 40 2 14 83
42 2
40 3 42 1 15
92
85 L5
90 L6
42 1 40 4 40 2 14 83
40 4 42 1 14 80 4
90 5 40 4 40 5 88 L6
91 4
42 2 40 3 42 1 15 92
85 L7
90 L8 40 4 40 2 14
8 87 L9
40 4 42 2 11 92
85 L11
90 L10
42 0 40 6 40 2 14 83
40 4 40 6 14 80 6
90 L11
40 6 40 3 22 86 L10
91 6 90 L9
40 4 42 1 14 80 4
90 L7 40 4 40 5 88 L8
91 4 97 103 0
| エントリ5 L1 'S' 'I' 'E' 'V' 'E'
セーブ4
LN 0
LN 0 LP 2 PLUS
スティンド
LN 0 LN 1 LP 2 PLUS STIND
Ln 2
LP 3 LN 1マイナス
店舗
ジャンプL5
LAB L6
LN 1 LP 4 LP 2 PLUS STIND
LP 4 LN 1 PLUS SP 4
LAB L5 LP 4 LP 5 ENDFOR L6
スタック4
LN 2 LP 3 LN 1マイナスストア
ジャンプL7
LAB L8 LP 4 LP 2 PLUS
RV JF L9
LP 4 LN 2マルチストア
ジャンプL11
LAB L10
LN 0 LP 6 LP 2 PLUS STIND
LP 4 LP 6 PLUS SP 6
LAB L11
LP 6 LP 3 LS JT L10
スタック6ラボL9
LP 4 LN 1 PLUS SP 4
LAB L7 LP 4 LP 5 ENDFOR L8
スタック4 RTRNエンドプロック0
| ; 手順のタイトル ; スタックフレーム(2つのパラメーターと2つのローカル変数) ; スタックに番号0を置きます ; さらに0を入れ、2番目のスタック要素を追加します ; スタックの一番上の配列にその下の値を書き込む ; 配列の最初の要素についても同じ ; スタックに2番を置きます ; 3番目のスタック要素の値から1を引く ; 結果をローカル変数に書き込みます ; ラベルL5に移動 ; ラベルタグL6 ; スタックの4番目の要素を取得し、このインデックスで配列に書き込みます1 ; スタック1の4番目の要素に追加し、結果を書き戻します ; L5:4番目のスタック要素<= 5番目の場合、ラベルL6に移動 ; スタック上に4つの要素があることを発表 ; 3番目のスタック要素の値から1を引く ; ラベルL7に移動 ; L8:スタックの4番目と2番目の要素を追加する ; このアドレスの値を読み取ります。 0の場合、L9に進みます ; 4番目の要素に2を掛けます ; ラベルL11に移動 ; タグラベルL10 ; スタックの6番目の要素を取得し、このインデックス0で配列に書き込みます ; スタックの4番目の要素に4番目の要素を追加し、結果を書き戻します ; タグラベルL11 ; スタックの7番目の要素が4番目よりも小さい場合は、ラベルL10に移動します ; スタックには6つの要素があります。 タグラベルL9 ; スタック1の4番目の要素に追加し、結果を書き戻します ; L10:4番目のスタック要素<= 5番目の場合、L8に移動 ; スタックには4つの要素があります。 手順の終わり
|
(スペースを節約するために、コマンドシーケンスは1行で記述されています。MartinはBCPLマニュアルでもまったく同じことを行っています。)
ソースパッケージBCPLをダウンロードします。
LETふるい(workvec、vecsize)BE
{
workvec!0:= 0
workvec!1:= 0
FOR i = 2 to vecsize-1 DO workvec!I:= 1
FOR i = 2 TO vecsize-1 DO
IF workvec!します
{LET j = 2 * i
j <vecsize DOの場合
{workvec!j:= 0
j:= j + i
}
}
}
OCODEの新しいバージョンでは、浮動小数点数のサポートが追加され(それぞれ、サポートされるオペコードのセットがほぼ2倍になりました)、
ENDFOR
オペコードが
ENDFOR
さ
ENDFOR
-代わりに、
LE JT
ペア
LE JT
生成されます。
「ユニバーサルマシン言語」の中で、OCODEは、そのラベルが特別な指示によって決定されるという点でユニークです。 プログラムを解釈するには、まずすべてをメモリにロードし、その中のラベルを見つける必要があります。
-そして、別のプログラムである
コードジェネレーターが 、このような擬似コードを含むファイルを最終プロセッサ用の実行可能プログラムに変換しました。 OCODEは、スペースと改行で区切られた10進数のテキストファイルとして保存されました。OCODEの開発中、ファイル形式を特定のバイトサイズにリンクすると、そのようなファイルの移植性が制限されました。

BCPL
(1)コンパイラはOCODEとして出荷されており、新しいプラットフォームに移植するために必要でした:
- 擬似コードインタープリターを手動で記述します(2) (任意の言語、BASICでも)。
- (3) BCPLで記述されたコードジェネレーターをプラットフォームに適合させます。
- インタープリター(2)の下でBCPLコンパイラー(1)を実行し、コードジェネレーター(3)にフィードし、コードジェネレーターの実行可能ファイル(4)を取得します。
- コンパイラー擬似コード(1)をコードジェネレーター(4)から追い出し、出力でコンパイラー実行可能ファイルを取得します。
このアプローチは、コンパイラを新しいプラットフォームに移植するために最低限の低レベルのプログラミングのみが必要であることを意味していました。 実際、BCPLの実装は1967年までに完了しました。数年前に始まったCPLの実装が完了する前です!
システムプログラミングに対するBCPLのメリットは、ケントンプソンにインスピレーションを与えて
Be言語を作成し、ケンの同僚であるデニスリッチーにインスピレーションを与えてCを作成しました。 BCPLから伝統が
{
中括弧
}
プログラムブロックを指定するようになり、BCPL
で最初のプログラム
「Hello、World!」 が 作成されました 。
「libhdr」を取得
LET開始()= VALOF
{writef( "Hello * n")
結果0
}
BCPLが歴史上ダウンした理由は私たちにとってより重要です:OCODEは最初の
普遍的な「命令セットアーキテクチャ」 (ISA)です。 「仮想マシン」。その機能により特定のハードウェアプラットフォームに関連付けられていません。 したがって、BCPLは、
「一度書き込み、どこでも実行」 (
WORA )
パラダイムに準拠する最初のプログラミング言語です。BCPLプログラムは、コンパイルされた形式で配布でき、OCODEコードジェネレーターが存在するプラットフォームで実行できます。
BCPL自体とそのOCODEは、英国以外では人気を博していませんが、
WORAの考え方は定着しています。 1974年、すでに大陸で、
ETHチューリッヒで 、ニクラウスヴィルトの指導の下で、「Pascal-P」が開発されました-中間「pコード」へのコンパイルと、この「pコード」の特定のハードウェアプラットフォーム用の実行可能コードへのコンパイル。 海洋の反対側である
UCSDの Pascal-Pに基づいて、
p-System (1978)OSが作成されました。コードジェネレーターはまったくありません
でした 。ベアメタルでの作業。 すべてのp-Systemシステムユーティリティは、クロスプラットフォームのp-codeの形式で提供され、p-Systemを新しいハードウェアプラットフォームに適合させるには、p-codeインタープリターを記述するだけで十分でした。
Pコードの例 0000:D8 p2_0:SLDL 1
0001:00 SLDC 0
0002:9A STO
0003:02 SLDC 2
0004:CC 03 STL 3
0006:DA p2_2:SLDL 3
0007:31 SLDC 49
0008:C8 LEQI
0009:A1 12 FJP p2_5
000B:D8 SLDL 1
000C:DA SLDL 3
000D:01 SLDC 1
000E:32 SLDC 50
000F:88 CHK
0010:01 SLDC 1
0011:95 SBI
0012:A4 01 IXA 1
0014:01 SLDC 1
0015:9A STO
0016:DA SLDL 3
0017:01 SLDC 1
0018:82 ADI
0019:CC 03 STL 3
001B:B9 F6 UJP p2_2
001D:02 p2_5:SLDC 2
001E:CC 03 STL 3
0020:DA p2_4:SLDL 3
0021:31 SLDC 49
0022:C8 LEQI
0023:A1 2F FJP p2_1
0025:D8 SLDL 1
0026:DA SLDL 3
0027:01 SLDC 1
0028:32 SLDC 50
0029:88 CHK
002A:01 SLDC 1
002B:95 SBI
002C:A4 01 IXA 1
002E:F8 SIND 0
002F:A1 1C FJP P2_6
0031:02 SLDC 2
0032:DA SLDL 3
0033:8F MPI
0034:CC 02 STL 2
0036:D9 p2_3:SLDL 2
0037:32 SLDC 50
0038:C9 LESI
0039:A1 12 FJP p2_6
003B:D8 SLDL 1
003C:D9 SLDL 2
003D:01 SLDC 1
003E:32 SLDC 50
003F:88 CHK
0040:01 SLDC 1
0041:95 SBI
0042:A4 01 IXA 1
0044:00 SLDC 0
0045:9A STO
0046:D9 SLDL 2
0047:DA SLDL 3
0048:82 ADI
0049:CC 02 STL 2
004B:B9 F4 UJP P2_3
004D:DA P2_6:SLDL 3
004E:01 SLDC 1
004F:82 ADI
0050:CC 03 STL 3
0052:B9 F2 UJP p2_4
0054:AD 00 P2_1:RNP 0
| ; 最初のパラメーターをスタックにプッシュします ; スタックに0を置きます ; スタックの一番上の値をその下のアドレスに書き込みます ; スタックに2番を置きます ; スタックの最上部の値をスタックフレームに(ローカル変数に)書き込む ; ローカル変数をスタックにプッシュします
; 49以下ですか? ; そうでない場合は、ラベルp2_5に移動します
; 配列の境界を確認してください:ローカル変数#3は1から50の間でなければなりません
; スタックの一番上の値から1を引く、つまり ローカル変数3から ; 配列要素へのポインタを計算します:index-前回の減算結果、 ; baseは最初のパラメーター、要素サイズは1ワード ; 計算ポインタによるユニットの書き込み
; ローカル変数3の値に1を追加します ; 結果をスタックフレームに書き戻す ; ラベルp2_2への無条件ジャンプ
; ローカル変数番号3にデュースを書き込む
; この変数は49以下ですか? ; そうでない場合は、ラベルp2_1に移動します
; 配列の境界を確認してください:ローカル変数#3は1から50の間でなければなりません
; 上記とまったく同じように、配列要素へのポインタを計算します ; 計算されたポインタからオフセット0のワードを読み取ります ; ゼロ値が読み取られた場合、ラベルp2_5に移動します
; ローカル変数3の値に2を掛けます ; スタックフレーム内に結果を書き込む
; この値は50未満ですか? ; そうでない場合は、ラベルp2_6に移動します
; 配列の境界を確認してください:ローカル変数#2は1から50の間でなければなりません
; 配列要素へのポインタを計算します:インデックスは1つ少なくなります ; ローカル変数2の値、ベースおよびサイズ-上記のように ; 計算されたポインターにゼロを書き込む
; ローカル変数2に変数3の値を追加します ; 結果をスタックフレームに書き戻す ; ラベルp2_3への無条件ジャンプ
; ローカル変数3の値に1を追加します ; 結果をスタックフレームに書き戻す ; ラベルp2_4への無条件ジャンプ ; スタックから呼び出し側プロシージャに0の値を返します
|
ソースパッケージpascalをダウンロードします。
const data_size = 50;
タイプdata_array =ブール値の配列[1..data_size];
手続きふるい(var workvec:data_array);
var i、j:整数;
始める
workvec [1]:= false;
for i:= 2 to data_size-1 do workvec [i]:= true;
for i:= 2 to data_size-1 do
workvec [i]の場合、開始
j:= 2 * i;
一方、j <data_sizeは開始します
workvec [j]:= false;
j:= j + i;
終わり
終わり
終わり;
機械語整数と浮動小数点数のみをサポートするOCODEと比較して、Pascalのpコードは、文字列、配列、「パック配列」(要素サイズが1ワード未満)など、はるかに多様なデータ型をサポートします。記録、セットなど。 動的メモリ割り当ても同様です。 もう1つの重要な違いは、プロシージャの「作業スタック」と、パラメータとローカル変数を含む「スタックフレーム」が分離されたことです。
コンパクトなpコードの場合、多くの命令には、小さく頻繁に使用される引数値の「短縮オペコード」があります。
p-Systemは非常に人気がありました。いくつかのApple IIモデルと、元のIBM PC(1981)を含むいくつかのIBMコンピューターがOSとしてp-Systemとともに
提供されました。 1979年から、Western Digitalはp-code実行がハードウェアに実装された
Pascal MicroEngineミニコンピューターをリリースしました。 したがって、抽象的な「ユニバーサルマシン言語」からのpコードは、実際のコンピューターの実際のマシン言語に変わりました。
p-Systemは90年代にはすでに市場から姿を消していたが、1996年にJames Goslingに
Java仮想マシンの作成を促すことに成功した。
「一度書くだけで、どこでも実行できます!」人気の広告スローガンに変わり、「ユニバーサルマシン言語」(
「バイトコード」 )の実装が2つの方向に並行して開発されました。
- ソフトウェア実装の高速化(JDK 1.1でのJITコンパイル 、1997)
- ハードウェア実装の試み:
当然のことながら、「バイトコードプロセッサ」のどれも-PコードでもJavaでも-商業的に成功しませんでした。 (これには、以前の
Intel iAPX 432 (1981)プロセッサ、Adaのバイトコードのハードウェア実装も含まれます。)Javaバイトコード、Wirthのpコード、およびOCODEは、すべて中間
指向であるため、すべて
スタック指向です。生成と解釈が最も簡単です。 「仮想マシン」スタックは制限されていません。 反対に、「鉄」プロセッサには固定数のレジスタがあります。コンパイラの最も重要なタスクの1つは、メモリアクセスができる限り実行されないようにレジスタ間でデータを分散することです。 「鉄」のデータ間の依存関係を追跡し、「鉄」レジスタに従ってデータを配布し、「鉄」の制限に適合するようにデータへのアクセスを再配置するには、非常に複雑なメカニズムが必要です。 同じレベルの半導体技術では、同じハードウェアで同じバイトコードを実行するよりも、単純なISA用のプロセッサを作成し、その上にバイトコード変換を実装する方が効率的です。 何度も何度も、夢のようなIT起業家は、「ユニバーサルマシン言語」を実際の言語に変えることは、技術的には可能ですが、商業的には見込みがないと確信するようになりました。
Javaバイトコードの例
2a
なる
3c
2a
03
03
54
2a
04
03
54
05
3d
1c
1b
a2 00 0d
2a
1c
04
54
84 02 01
a7 ff f4
05
3d
1c
1b
a2 00 23
2a
1c
33
99 00 17
05
1c
68
3e
1d
1b
a2 00 0e
2a
1d
03
54
1d
1c
60
3e
a7 ff f3
84 02 01
a7 ff de
b1
| プライベート静的ボイドシーブ(ブール[]);
コード:
0:aload_0 //インデックス0のスタックフレームから取得したリンクをスタックに配置します。 パラメータ
1:arraylength //渡された配列の長さをスタックにプッシュします
2:istore_1 //スタックから整数をインデックス1のスタックフレームに保存します
3:aload_0
4:iconst_0 //スタックに整数を置く-定数0
5:アイコン
6:bastore //インデックス0でバイトまたはブール配列0に書き込む
7:aload_0
8:iconst_1
9:アイコン
10:bastore //同じ配列0にインデックス1で書き込む
11:iconst_2
12:istore_2 //整数値2をインデックス2のスタックフレームに保存します
13:iload_2 //保存した値をスタックに配置します
14:iload_1 //ローカル変数番号1をスタックに配置します
15:if_icmpge 28 // 2つの整数を比較します。 value> =変数の場合、命令28に進みます
18:aload_0
19:iload_2
20:iconst_1
21:bastore //ローカル変数のインデックスにあるユニットをバイトまたはブール配列に書き込む
22:iinc 2、1 //ローカル変数2を1つ増やす
25:goto 13 //命令13に移動
28:iconst_2
29:istore_2 //ローカル変数2に整数2を書き込む
30:iload_2
31:iload_1
32:if_icmpge 67 //変数番号2が変数番号1以上の場合、命令67に進む
35:aload_0
36:iload_2
37:baload //この変数からインデックスによってバイトまたはブール配列の要素を読み取ります
38:ifeq 61 //読み取り項目がゼロの場合、命令61に進みます
41:iconst_2
42:iload_2
43:imul //ローカル変数No. 2の値に2を掛ける
44:istore_3 //結果をインデックス3のスタックフレームに保存する
45:iload_3
46:iload_1
47:if_icmpge 61 //ローカル変数#3> =変数#1の場合、命令61に進む
50:aload_0
51:iload_3
52:アイコン
53:bastore //変数No. 3からのインデックスによってバイトまたはブール配列0に書き込みます
54:iload_3
55:iload_2
56:iadd //ローカル変数No. 3変数No. 2の値に追加
57:istore_3 //結果をスタックフレームに書き戻す
58:goto 45 //命令45に移動
61:iinc 2、1 //ローカル変数2を1つ増やす
64:goto 30 //命令30に移動
67:return //プロシージャから戻る
|
ソースコード:
private static void sieve(boolean[] workvec) { int vecsize = workvec.length; workvec[0] = false; workvec[1] = false; for(int i = 2; i<vecsize; i++) workvec[i] = true; for(int i = 2; i<vecsize; i++) if(workvec[i]) { int j = 2 * i; while(j < vecsize) { workvec[j] = false; j = j + i; } } }
バイトコードはWirthのpコードよりもさらに設計されており、コンパクトに設計されています。たとえば、
iflt
(ゼロと条件
SLDC 0; GEQI; FJP
比較)は3つの命令
SLDC 0; GEQI; FJP
対応し
SLDC 0; GEQI; FJP
SLDC 0; GEQI; FJP
SLDC 0; GEQI; FJP
、および
iinc
は4つの
SLDL; SLDC; ADI; STL
命令を置き換え
SLDL; SLDC; ADI; STL
SLDL; SLDC; ADI; STL
SLDL; SLDC; ADI; STL
ただし、反対の例があります。 新しいISAとそのコンパイラを作成し、新しいプラットフォームに必要な最小限のソフトウェアが現れるのを待つよりも、既存のプログラムが新しいプロセッサで実行できるように、人気のISAを実装することはプロセッサメーカーにとってはるかに有益です。
IA-32は長い間、このような「ユニバーサルマシン言語」のままでした。既存のソフトウェアの量は、新しいISAへの切り替えの利点を上回り、
Pentium Pro (1995)以降、Intelプロセッサは本質的に古いISAの「ハードウェアエミュレーション」を実装しました。 8つの「クラシック」レジスタで動作するIA-32命令は、プロセッサにより40の「鉄」レジスタで動作する内部
RISCマイクロ
コマンドに変換されます。 プロセッサ内のパイプラインは、これらのRISCマイクロコマンドをいくつかのスレッドに実行します-これがデータ間の依存関係を壊さない場合、マイクロコマンドを任意に再配置します。 開発チームにLinus Torvaldsを含む
Transmeta Crusoeプロセッサ (2000)では、ISAハードウェアエミュレーションのアイデアがさらに開発されました。箱から出してIA-32をサポートしましたが、他のISAと連携するように再プログラムできます-これを実証するためにTransmetaには、ハードウェアJavaバイトコードをサポートするCrusoeの「広告サンプル」がありました。
IA-32マシンコードの例機械コード | アセンブラー |
---|
55
89 e5
56
66 c7 01 00 00
b8 02 00 00 00
02 00 00 00
eb 05
c6 04 31 01
46
39 d6
7c f7
eb 01
40
39 d0
7d 17
80 3c 01 00
74 f5
8d 34 00
eb 06
c6 04 31 00
01 c6
39 d6
7c f6
eb e4
5e
5d
c3
| .type _ZL5sievePbi、@関数
_ZL5sievePbi:#ecxに渡される配列へのポインター、edxへの配列の長さ
#%エントリ
pushl%ebp#以前のebp値を保持
movl%esp、%ebp#ebpは現在のスタックフレームを指すようになりました
pushl%esi#以前のesi値を保持
movw $ 0、(%ecx)#ecxにゼロワードを書き込む
movl $ 2、%eax#(つまり、配列の2つの要素が1つの命令でリセットされます)
movl $ 2、%esi#eaxとesiで2つ節約
jmp .LBB1_1#ラベル.LBB1_1に移動
.LBB1_2:#%for.body
movb $ 1、(%ecx、%esi)#ecx + esiにシングルバイトを書き込む
incl%esi#esiを1つ増やします
.LBB1_1:#.for.cond
cmpl%edx、%esi#esiとedxを比較
jl .LBB1_2#edx <esiの場合、ラベル.LBB1_2に移動
jmp .LBB1_4#それ以外の場合、ラベル.LBB1_4に移動
.LBB1_3:#%for.inc.11
incl%eax#eaxを1つ増やします
.LBB1_4:#.for.cond.4
cmpl%edx、%eax#eaxとedxを比較
jge .LBB1_9#edx> = esiの場合、ラベル.LBB1_9に移動
#%for.body.7
cmpb $ 0、(%ecx、%eax)#ecx + esiでゼロバイトと比較
je .LBB1_3#ゼロの場合、.LBB1_3に移動
#%if.then
leal(%eax、%eax)、%esi#eaxの二重値をesiに書き込む
jmp .LBB1_7#ラベル.LBB1_7に移動
.LBB1_8:#%while.body
movb $ 0、(%ecx、%esi)#ゼロバイトをecx + esiに書き込む
addl%eax、%esi#eaxをesiに追加
.LBB1_7:#%while.cond
cmpl%edx、%esi#esiとedxを比較
jl .LBB1_8#edx <esiの場合、ラベル.LBB1_8に移動
jmp .LBB1_3#それ以外の場合、ラベル.LBB1_3に移動
.LBB1_9:#%for.cond.cleanup.6
popl%esi#古いesi値を復元
popl %ebp # ebp
retl #
|
, , .
「ユニバーサルマシン言語」のハードウェア実装のこれまでのところ成功した例で、実装されたISAがスタックされずに登録されたことは偶然ではありません。 OCODEを置き換えるために、Martin Richards自身がINTCODE(1972)と呼ばれる新しいレジストリuni-ISAを開発しました。 INTCODEは非常にシンプルです。6つのレジスタ、8つの操作、およびいくつかのアドレッシングモードがサポートされています。命令は、アドレス指定モードに応じて、1ワードまたは2ワードを占有します。 1980年に、Martinは16ビットマイコン用のこのuni-ISAのバージョンを開発しました。新しいuni-ISA-まだ6つのレジスタがありますが、より複雑で直交性の低いコマンドセットがあります-はCintcodeと呼ばれ、非常にコンパクトでした。広告リーフレットで承認されましたCintcodeプログラムは、通常、6502マシンコードにコンパイルされるよりも3倍少ないメモリを使用します。コンピューターの場合、BBC MicroおよびAmigaは、マシンコードとともにCintcodeの実行をサポートする一般的なOSでした。Cintcodeの例P
— ;
P[3]
,
P[4]
— .
0: 10 L0 ; A := 0
1: DB ST0P3 ; P[3][0] := A ( )
2: DD ST1P3 ; P[3][1] := A ( )
3: 0F LM1 ; A := -1
4: C4 AP4 ; A := A + P[4] ( )
5: A6 SP6 ; P[6] := A ( )
6: 12 L2 ; B := A; A := 2
7: A5 SP5 ; P[5] := A
8: 5C0A JLS 10 ; IF B<A GOTO 19
10: 11 L1 ; A := 1
11: 83 LP3 ; B := A; A := P[3]
12: 9A STP5 ; P[5][A] := B ( P[5])
13: B5 XCH ; A := B
14: C5 AP5 ; A := A + P[5]
15: A5 SP5 ; P[5] := A ( P[5] )
16: 86 LP6 ; B := A; A := P[6]
17: 9CF8 JLE -8 ; IF B<=A GOTO 10
19: 0F LM1 ; A := -1
20: C4 AP4 ; A := A + P[4]
21: A6 SP6 ; P[6] := A ( )
22: 12 L2 ; B := A; A := 2
23: A5 SP5 ; P[5] := A
24: 5C1B JLS 27 ; IF B<A GOTO 52
26: 83 LP3 ; A := P[3]
27: D8 RVP5 ; A := P[5][A] ( P[5])
28: 1E11 JEQ0 17 ; IF A=0 GOTO 46
30: 85 LP5 ; A := P[5]
31: 12 L2 ; B := A; A := 2
32: 34 MUL ; A := A * B ( P[5])
33: A7 SP7 ; P[7] := A
34: 84 LP4 ; B := A; A := P[4] ( A )
35: BC0A JGE 10 ; IF B>=A GOTO 46
37: 10 L0 ; A := 0
38: 87 LP7 ; B := A; A := P[7]
39: 98 STP3 ; P[3][A] := B ( P[7])
40: 87 LP7 ; A := P[7]
41: C5 AP5 ; A := A + P[5]
42: A7 SP7 ; P[7] := A ( P[7] P[5])
43: 84 LP4 ; B := A; A := P[4] ( A )
44: 5CF8 JLS -8 ; IF B<A GOTO 37
46: 11 L1 ; A := 1
47: C5 AP5 ; A := A + P[5]
48: A5 SP5 ; P[5] := A ( P[5] )
49: 86 LP6 ; B := A; A := P[6] (A 1 )
50: 9CE7 JLE -25 ; IF B<=A GOTO 26
52: 7B RTN
(2014) BCPL Cintcode System BCPL. Cintcode ( A B ) ( A ).
積み重ねられたuni-ISAの開発の頂点は、または、反対側から見ると、開発の行き止まりです。MSIL(2001;正式にCILと呼ばれています)です。MSILはJavaバイトコードに非常に似ていますが、いくつかの追加機能を追加します。私の知る限り、ハードウェアにMSILを実装する試みは行われていません。マイクロソフトは、クロスプラットフォームでフル機能のWebアプリケーションを作成するためのJavaの代替案を提案しようともしていません。MSILは「Microsoftプラットフォームのマシン言語」のままであり、他にはありません。MSILの例.method private hidebysig static
void sieve(bool[] workvec) cil managed
{
.maxstack 3
.locals init ([0] int32 vecsize,
[1] int32 i,
[2] int32 V_2,
[3] int32 j)
IL_0000: /* 02 | */ ldarg.0 // №0 ()
IL_0001: /* 8E | */ ldlen // ( )
IL_0002: /* 69 | */ conv.i4 // int32 ( )
IL_0003: /* 0A | */ stloc.0 // №0
IL_0004: /* 02 | */ ldarg.0 //
IL_0005: /* 16 | */ ldc.i4.0 // 0 ( )
IL_0006: /* 16 | */ ldc.i4.0
IL_0007: /* 9C | */ stelem.i1 //
IL_0008: /* 02 | */ ldarg.0
IL_0009: /* 17 | */ ldc.i4.1 // 1 ( )
IL_000a: /* 16 | */ ldc.i4.0
IL_000b: /* 9C | */ stelem.i1 // (int8) 1
IL_000c: /* 18 | */ ldc.i4.2
IL_000d: /* 0B | */ stloc.1 // №1
IL_000e: /* 2B | 08 */ br.s IL_0018 // IL_0019
IL_0010: /* 02 | */ ldarg.0
IL_0011: /* 07 | */ ldloc.1 //
IL_0012: /* 17 | */ ldc.i4.1
IL_0013: /* 9C | */ stelem.i1 //
IL_0014: /* 07 | */ ldloc.1
IL_0015: /* 17 | */ ldc.i4.1
IL_0016: /* 58 | */ add //
IL_0017: /* 0B | */ stloc.1 //
IL_0018: /* 07 | */ ldloc.1
IL_0019: /* 06 | */ ldloc.0 // №0
IL_001a: /* 32 | F4 */ blt.s IL_0010 // №1 < №0, IL_0010
IL_001c: /* 18 | */ ldc.i4.2
IL_001d: /* 0C | */ stloc.2 // №2
IL_001e: /* 2B | 1B */ br.s IL_003b // IL_003b
IL_0020: /* 02 | */ ldarg.0
IL_0021: /* 08 | */ ldloc.2
IL_0022: /* 90 | */ ldelem.i1 //
IL_0023: /* 2C | 12 */ brfalse.s IL_0037 // , IL_0037
IL_0025: /* 18 | */ ldc.i4.2
IL_0026: /* 08 | */ ldloc.2
IL_0027: /* 5A | */ mul // №2
IL_0028: /* 0D | */ stloc.3 // №3
IL_0029: /* 2B | 08 */ br.s IL_0033 // IL_0033
IL_002b: /* 02 | */ ldarg.0
IL_002c: /* 09 | */ ldloc.3
IL_002d: /* 16 | */ ldc.i4.0
IL_002e: /* 9C | */ stelem.i1 // 0 №3
IL_002f: /* 09 | */ ldloc.3
IL_0030: /* 08 | */ ldloc.2
IL_0031: /* 58 | */ add // №2 №2
IL_0032: /* 0D | */ stloc.3 // №2
IL_0033: /* 09 | */ ldloc.3
IL_0034: /* 06 | */ ldloc.0
IL_0035: /* 32 | F4 */ blt.s IL_002b // №3 < №0, IL_002b
IL_0037: /* 08 | */ ldloc.2
IL_0038: /* 17 | */ ldc.i4.1
IL_0039: /* 58 | */ add // №2
IL_003a: /* 0C | */ stloc.2 //
IL_003b: /* 08 | */ ldloc.2
IL_003c: /* 08 | */ ldloc.0
IL_003d: /* 32 | E1 */ blt.s IL_0020 // №2 < №0, IL_0020
IL_003f: /* 2A | */ ret //
}
C# Java
boolean
→
bool
. Java- MSIL : -, , . -, (/ , , ..), MSIL,
add
, «», .. ; (, — int32) . , MSIL JIT-, MSIL- .
, MSIL «»,
iflt
iinc
.
この千年紀には、「ユニバーサルマシン言語」に対するスタックの競合はなくなりました。
プレゼント
2000年、UIUCの大学院生であるChris Luttner は卒業プロジェクトとして、Javaのコードを普遍的な「中間表現」(IR)に変換する完全に独立した「フロントエンド」で構成される「ユニバーサルコンパイラ」LLVMの開発を開始しました。バックエンド。IRを特定のISAの実行可能コードに変換します。中間表現LLVM-IRとして、単一の割り当てフォームが選択されました(「SSAフォーム」):各変数には値が1回割り当てられ、プログラムの動作中は変更されません。このフォームは、データ間の依存関係の追跡を簡素化し、個々の命令の置換と置換、重複または「デッド」命令の検出と削除などを単純化します。LLVM-IRの例(BB), ( ). BB . — . BB
, .. BB, .
; Function Attrs: minsize noinline nounwind optsize
define internal fastcc void @_ZL5sievePbi(i8* nocapture %workvec, i32 %vecsize) #0 {
; #0 -- ( )
entry:
store i8 0, i8* %workvec, align 1, !tbaa !1 ;
%arrayidx1 = getelementptr inbounds i8, i8* %workvec, i32 1 ;
store i8 0, i8* %arrayidx1, align 1, !tbaa !1 ;
br label %for.cond ; BB
for.cond: ; preds = %for.body, %entry
%i.0 = phi i32 [ 2, %entry ], [ %inc, %for.body ]
; %i.0 2, BB %entry, %inc, %for.body
%cmp = icmp slt i32 %i.0, %vecsize
; %i.0 %vecsize , %cmp 1 0
br i1 %cmp, label %for.body, label %for.cond.4 ; %for.body %for.cond.4
for.body: ; preds = %for.cond
%arrayidx2 = getelementptr inbounds i8, i8* %workvec, i32 %i.0 ; %i.0-
store i8 1, i8* %arrayidx2, align 1, !tbaa !1 ;
%inc = add nuw nsw i32 %i.0, 1 ; %inc %i.0
; nuw nsw , ( ),
br label %for.cond ; BB
for.cond.4: ; preds = %for.cond, %for.inc.11
%i3.0 = phi i32 [ %inc12, %for.inc.11 ], [ 2, %for.cond ]
; %i3.0 2, BB %for.body, %inc12, %for.inc.11
%cmp5 = icmp slt i32 %i3.0, %vecsize ; %i3.0 %vecsize
br i1 %cmp5, label %for.body.7, label %for.cond.cleanup.6 ; , %i3.0 %vecsize
for.cond.cleanup.6: ; preds = %for.cond.4
ret void ; ,
for.body.7: ; preds = %for.cond.4
%arrayidx8 = getelementptr inbounds i8, i8* %workvec, i32 %i3.0
%0 = load i8, i8* %arrayidx8, align 1, !tbaa !1, !range !5 ; %i3.0
%tobool = icmp eq i8 %0, 0 ;
br i1 %tobool, label %for.inc.11, label %if.then
if.then: ; preds = %for.body.7
%mul = shl nsw i32 %i3.0, 1 ; %mul %i3.0
br label %while.cond ; BB
while.cond: ; preds = %while.body, %if.then
%j.0 = phi i32 [ %mul, %if.then ], [ %add, %while.body ]
%cmp9 = icmp slt i32 %j.0, %vecsize ; %j.0 %vecsize
br i1 %cmp9, label %while.body, label %for.inc.11 ; %j.0 < %vecsize, %while.body
while.body: ; preds = %while.cond
%arrayidx10 = getelementptr inbounds i8, i8* %workvec, i32 %j.0
store i8 0, i8* %arrayidx10, align 1, !tbaa !1 ; %j.0-
%add = add nsw i32 %j.0, %i3.0 ; %add %j.0 %i3.0
br label %while.cond ; BB
for.inc.11: ; preds = %while.cond, %for.body.7
%inc12 = add nuw nsw i32 %i3.0, 1 ; %inc12 %i3.0
br label %for.cond.4
}
IA-32 LLVM IR — , , , BB .
!tbaa !1
—
alias analysis ;
!range !5
— (
bool
— 0 1).
, — «» MSIL.
C++:
static void sieve(bool workvec[], int vecsize) { workvec[0] = false; workvec[1] = false; for(int i = 2; i<vecsize; i++) workvec[i] = true; for(int i = 2; i<vecsize; i++) if(workvec[i]) { int j = 2 * i; while(j < vecsize) { workvec[j] = false; j = j + i; } } }
LLVM-IRは、ユニISAとしてではなく、主にフロントエンドからバックエンドにコードを転送するための効果的な方法として開発されたため、ユニISAの役割には多くの欠点があります。まず、ターゲットプラットフォームの機能がLLVM-IRの生成に影響する可能性があります。32ビットシステム用のプログラムは、64ビットシステム用のプログラムとは別のLLVM-IRにコンパイルされます。また、Windows用のプログラムは、Linux用のプログラムとは異なるLLVM-IRでコンパイルされます。次に、LLVM-IRは不安定です。LLVM開発者は、重要なプラットフォームで重要な言語ツールをより効率的にコンパイルできる場合、新しい命令を追加したり、既存の命令の値を変更したりできます。これは、特定のバージョンのフロントエンドによって生成されたLLVM-IRは、対応するバージョンのバックエンドでのみ使用でき、他のバージョンでは使用できないことを意味します。第三に、LLVM-IRでは、コードに「未定義の動作」を提示できます-各バックエンドには、プログラムから最大限のパフォーマンスを「絞り出す」ために、便利な方法でそのようなコードをブロードキャストする権利があります。 CまたはC ++で未定義の動作を伴うコードは、通常、LLVM-IRで未定義の動作を伴うコードにコンパイルされます。実際、このようなコードのセマンティクスは、特定のプラットフォームに最適な方法で、フロントエンドではなくバックエンドによって決定されます。当然のことながら、未定義の動作を伴うコードは、uni-ISAが昇格される「1回書き込み、どこでも実行」というパラダイムと矛盾します。それにもかかわらず、LLVMのアクティブな開発者の1人であるGoogleは、LLVMがサポートする多くのPLのいずれかで書かれたクロスプラットフォームアプリケーションの配布にLLVM-IRが適切な形式であると考えました。バージョン31(2013)以降、Google ChromeにはPNaClのサポートが含まれています-LLVM-IRのサブセットで、安定した命令セマンティクスを備えており、プラットフォーム固有の構成と最適化はありません。15年前にまったく同じ目的でクロスプラットフォームのフル機能Webアプリケーションを作成するために発明されたJavaアプレットよりもPNaClアプリケーションの方が優れているのはなぜでしょうか。 (このトピックの冒頭のKDPVはまさにトピックです。)私はPNaClの2つの利点を知っています。まず、Javaスタックバイトコードと比較したLLVM-IRは、ブラウザーがPNaClアプリケーションを特定のターゲットプラットフォームのマシンコードに変換するときのコード最適化を簡素化します。 2番目の、そして私が理解するように、主な議論はLLVMのオープン性とライセンスされた清潔さです。そのホスト(SunとOracle)はJavaサポートに関してGoogleを含む彼らが出会ったすべての人を訴えました。対照的に、LLVMとそのIRは完全にオープンです。そして一方では、GoogleはPNaClコンパイラであらゆる種類のパンと他のLLVM開発参加者によって実装された新しい最適化で無料で受け取ります。一方、他の開発者は、ブラウザにPNaClアプリケーションのサポートを追加する権利があり、訴訟を恐れることはありません。これまでのところ、Googleの例に従ってPNaClのサポートを実装する意思はありませんでした。それどころか、同じ目的のためのMozilla-クロスプラットフォームでフル機能のWebアプリケーションを作成する-は、asm.js(2013)と呼ばれる独自のuni-ISAを開発しました。Asm.js生成は、LLVMの新しいバックエンドとして実装されました。 asm.jsとPNaClの作業はほぼ同時に行われ、Google Chromeではasm.jsのサポートはPNaClのサポートよりも早く登場しました-バージョン28(2013)以降。asm.jsは非常に洗練されたアイデアを実装しています。そのプログラムはJavaScriptの(大幅に削除された)正しいコードです。したがって、このようなプログラムはJavaScriptをサポートするブラウザーで動作するはずです。一方、asm.jsコンストラクトを認識し、ターゲットプラットフォームのマシンコードにオンザフライでコンパイルできるブラウザは、JavaScriptを「額で」実行するブラウザよりも何倍も高速にプログラムを実行します。したがって、両方の古いブラウザはasm.jsで新しいコードを実行でき、新しいブラウザは「クラシック」JavaScriptで古いコードを実行できます。スクリプト"use asm";
にプロローグがない場合、「古い方法で」実行されます。Asm.jsの例C++, , Emscripten:
function __ZL5sievePbi($workvec,$vecsize) { $workvec = $workvec|0;
興味深いことに、開発中のasm.jsがJavaScriptの制限(64ビット数、マルチスレッド、SIMD命令などのサポートの欠如)に「遭遇」した場合、欠落している構造がJavaScript標準に追加されました。したがって、asm.jsと標準JavaScriptとの互換性は、asm.js開発者だけでなく、言語標準開発者の努力の結果です。すでに述べた理由に加えて、Javaアプレットを使用してLLVMに基づく新しいテクノロジーを使用する代わりに、asm.jsサポーターは次の引数を提供します。「JavaScriptには仮想マシンが組み込まれているため、もう1つ追加すると、接続APIの2番目のセットが表示され、DOM、ネットワーク、センサー、入力デバイスなどに仮想マシンがアクセスできるようになります。このために何かを犠牲にしなければなりません。たとえば、仮想マシンのプロセスは、利用可能なリソースをどのように分散しますか?この質問に答えるのは思ったより難しいです。」 (オリジナルのスペル)特に、これはエンジンの新機能がasm.jsと「クラシック」JavaScriptから同時に利用できることを意味します。2回実装する必要はありません。未来
GoogleとAppleが依存していた生のLLVM-IRと比較して、asm.jsには多くの利点があります。しかし、欠点もあります。まず、asm.jsのコードのサイズは非常に大きくなっています。2つの整数を追加すると20バイト以上かかります。第二に、asm.jsコードを実行するには、再解析する必要があります。JavaScript構文を使用してコードからコードを生成し、このコードからASTを復元するのではなく、ASTの形式でコンパイルの結果を保存する方がはるかに効率的です。そのため、2015年の夏に、WebAssemblyという新しい「ユニバーサルマシン言語」が登場しました。asm.jsの長所の一部(JavaScriptエンジンとの統合など)を保持し、2つの欠点を解消し、古いJavaScriptエンジンとの直接の互換性を拒否しました(vsbのコメント、または、ブラウザでゲームがまったく起動しなかったため、または、1 fpsで始まりました-違いはありません、とにかく再生できません。」)代わりに、WebAssembly開発者は「パテ」 -WebAssemblyでコードを読み取り、その場でコードを生成するJavaScriptライブラリ古典的な「asm.js; そして彼の古いバージョンのブラウザはすでに実行方法を知っています。WebAssemblyの例C++, , WebAssembly LLVM:
_Z5sievePbi:
.param i32 # $0 $1
.param i32
.local i32, i32, i32, i32 # $2, $3, $4, $5
# %entry
block BB0_9 # --
i32.const $2, 0 #
i32.store8 $0, $2 #
i32.const $3, 1
i32.add $push0, $0, $3 # --
i32.store8 $pop0, $2 #
i32.const $4, 2
set_local $5, $4
BB0_1: # %for.cond
loop BB0_3
i32.ge_s $push1, $5, $1 # --
br_if $pop1, BB0_3
# %for.body
i32.add $push7, $0, $5 # $pushN $popN -- ""
i32.store8 $pop7, $3 # ( )
i32.add $5, $5, $3 #
br BB0_1
BB0_3: # %for.cond.4
loop BB0_9
block BB0_8
block BB0_4
block BB0_3 # , (? ?)
i32.lt_s $push2, $4, $1
br_if $pop2, BB0_4
br BB0_9
BB0_4: # %for.body.7
i32.add $push3, $0, $4
i32.load8_u $5, $pop3 # $4
i32.eq $push4, $5, $2 #
br_if $pop4, BB0_8
# %if.then
i32.shl $5, $4, $3 # $4 , ..
BB0_6: # %while.cond
loop BB0_8
i32.ge_s $push5, $5, $1
br_if $pop5, BB0_8
# %while.body
i32.add $push6, $0, $5
i32.store8 $pop6, $2
i32.add $5, $5, $4
br BB0_6
BB0_8: # %for.inc.11
i32.add $4, $4, $3
br BB0_3
BB0_9: # %for.cond.cleanup.6
return
LLVM-IR IA-32, ; WebAssembly , .
, ,
VEG - —
« WebAssembly .» — .
GoogleはWebAssemblyの主要な開発者の1つになりました-LLVMに基づいた新しい「ユニバーサルマシン言語」では、PNaClの開発中に得られたすべての経験が考慮されます。彼らは自分でPNaClを開発するつもりはありません。もう1つの主要な開発者であるMozillaは、asm.jsとは異なり、WebAssemblyが構文的にも意味的にも特定のPLに接続されていないことを強調しています。 10年か2年でJavaScriptが忘却の対象になり、ブラウザーはソースコードのスクリプトをサポートしなくなります。代わりに、WebAssemblyはブラウザーのWebアプリケーションのネイティブ形式になります。ソフトウェア開発者が複数ページのリストをBASICとPascalに配布から実行可能コードの配布に切り替えた1980年代に、PCプログラムはこの「飛躍的な進歩」を遂げました。少なくとも「これをすべてコンパイルして実行するにはどうすればよいか」というレベルで、単一のプログラミング言語を知らなくてもPCを使用できるようになりました。ほとんどのWebアプリケーションユーザーは、JavaScriptについてはまったく知りません。では、Webアプリケーションをソースコードとして配布する意味は何ですか?
モバイル機器メーカーが復活しました。ハードウェアに新しい「ユニバーサルマシン言語」でコードを実装すると、モバイルデバイスを取得するためにWebアプリケーションのパフォーマンスが大幅に向上する可能性があります。 WebAssemblyは、高レベルの機械語ですが、スタック言語ではありません。したがって、そのハードウェア実装は、スタックされたuni-ISAを実装する以前の試みよりも成功する可能性があります。最も重要なことは、x86が当時のPCの世界を変えたように、すべてのモバイルアプリケーション用の単一の機械語でエコシステム全体を変えることができることです。 「x86覇権」以前は、PC向けのプログラムは、さまざまなプラットフォームで多かれ少なかれ互換性のあるバージョンでリリースされていました。たとえば、Prince of Persiaは、Amiga、Amstrad、Apple II、Atari、FM Towns、IBM PC、Macintosh Quadra、NEC PC-9801、SAMCoupé、Sharp X68000。しかし、ある時点から「PC用プログラム」というフレーズは「x86用プログラム」を意味し始めました。同様に、「単一のモバイルISA」は、ソフトウェア開発者、デバイスメーカー(OEM)、およびプロセッサメーカーのリンクを解除します。OEMは、デバイスに任意のメーカーのプロセッサを選択できます。メーカー;開発者は、異なるプラットフォーム間でアプリケーションを転送するためにお金を費やす必要がなくなります。最終的に、必要なプログラムのいずれかが自分のデバイスで起動されると、ユーザーが勝ちます。同様に、「単一のモバイルISA」は、ソフトウェア開発者、デバイスメーカー(OEM)、およびプロセッサメーカーのリンクを解除します。OEMは、デバイスに任意のメーカーのプロセッサを選択できます。メーカー;開発者は、異なるプラットフォーム間でアプリケーションを転送するためにお金を費やす必要がなくなります。最終的に、必要なプログラムのいずれかが自分のデバイスで起動されると、ユーザーが勝ちます。同様に、「単一のモバイルISA」は、ソフトウェア開発者、デバイスメーカー(OEM)、およびプロセッサメーカーのリンクを解除します。メーカー;開発者は、異なるプラットフォーム間でアプリケーションを転送するためにお金を費やす必要がなくなります。最終的に、必要なプログラムのいずれかが自分のデバイスで起動されると、ユーザーが勝ちます。開発者は、異なるプラットフォーム間でアプリケーションを転送するためにお金を費やす必要がなくなります。最終的に、必要なプログラムのいずれかが自分のデバイスで起動されると、ユーザーが勝ちます。開発者は、異なるプラットフォーム間でアプリケーションを転送するためにお金を費やす必要がなくなります。最終的に、必要なプログラムのいずれかが自分のデバイスで起動されると、ユーザーが勝ちます。一方、単一の機械語はプロセッサメーカーに厳格なフレームワークを課します。「シングルISA」に受け入れられ、他のメーカーによって実装されるまで、新しい機能を新しいプロセッサに追加することはできません。さらに、統一された機械語の開発により、メーカーの1つが言語に特に有益な機能を言語にプッシュしようとすると、状況は避けられません。たとえば、Intelプロセッサの整数除算命令は、ゼロで除算するときに例外をスローします。 ARMプロセッサで-結果としてゼロを返します。ARMエンジニアは、除数テストをコンパイラにシフトし、除数が意図的にゼロでない場合に除算を高速化する方が良いと考えました。第三に、グラフィックスのサポートはどうですか? WebAssembly は試してさえいませんGPUに対して単一のISAを提供します-異なるメーカーの好みの違いは、IntelとARMのコマンドシステム間よりもさらに大きくなります。ただし、これらの考慮事項はすべて、WebAssemblyが根付くという事実に基づいており、PNaClのように数年後には放棄されず、JavaやMSILのような別のニッチで隔離されることはありません。見てみましょう。