フラグ色々
&
その遅延評価
-仮想機械インタプリタ書きのための-
2015/JUN/06
たけおか@AXE/たけおかラボ
@takeoka
たけおかって誰?●
1970年代から、並列計算&記号処理に興味あり。1977年,電卓500ステップで「#○×」を打つプログラ
ムを書く
●
1980年代、UNIXの仮名漢字変換システムWnnや Temporal Prolog(時相論理Prolog)を作ったり。国
産X Window端末などの設計開発に携わる。
●
1990年ごろ、豊橋技科大 湯淺研にて、1024PE規模の超並列計算機“SM-1”のLSIハードウェア、基本
ソフトウェアの設計開発に携わる。
●
1992年~ AXE; OSとコンパイラとか。
●
大きなものから小さなものまで… 最近は AI もやってます
実時間Linux極小Lisp
ザウルスのOS
デジカメのOS
組込みプレス No.8 (2007/AUG) 読者モデル
湯淺研 SM-1:1024PE超並列計算機
スパコン研究の低レイヤ
●
プログラム・カウンタが本当に存在しないピュアなデータフローマシン
が好き
フラグを知っているか?
●
最近、RISCだと「フラグ」見ないなぁ… (嘘。RISCでも、フラグはあります)
– Itaniumは、1bitをストアするレジスタ群
●
Sign
– 符号,ワードの最上位ビット
– これは、不要か… MSB(最上位ビット)をテストするだけ
●
Zero
– 演算結果が0か? 基本的
●
Carry
– 加算して、桁上がりがワードから溢れたビット
●
Borrow
– 減算で、桁下りが発生した(上位桁からの借り)
●
Half Carry (Half Borrow)
– BCD加(減)算で、4bitごとの桁上がり(借り)を記憶
●
Parity
– ワード内の全ビットのXOR結果 (これは今どき、不要かな…)
●
Overflow
– Sparc,Z80,6800にはある。(8080には無い)
Carryフラグを知っているか?
●
Carry
– 加算して、桁上がりがワードから溢れたビット
– Carry は、最も時間が掛かる
– 通常の計算機では、adderを抜ける時間が、pipelineの速度を決めている
– Carryは、adderの一番上。Carryが上がってくるのは遅い
●
人間が、下の桁から計算するのと同様。adderも下から値が決まる
●
Look ahead 手法で、数桁づつキャリを先出しする方法はある
●
Borrow
– Carryと同様
– 減算は、加算に還元される
●
減算は、加算に還元
– 1の補数表示: -n == ~n
●
m-n == m+(~n) + Borrow
– 2 の補数表示: -n == (~n) +1
●
m-n == m+((~n)+1) + Borrow
●
ここで、 Borrow == ~Carry (1の時、借りなし)
●
http://web.sfc.keio.ac.jp/~rdv/keio/sfc/teaching/architecture/computer-architecture-2013/lec07-cache.html より引用
ここの遅延
(抜ける時間)が
パイプラインの
上限周波数を
決める
周波数=1/時間
教科書アーキテクチャ MIPS
Carry※Carryは、私が書き加えました
Carryフラグを知っているか?
●
Carry/Borrow flag
– MIPSは、削除してしまった
– carryを作るのは、時間が掛かるから
●
Cフラグ無しCarryの判定法 (「ハッカーのたのしみ」より)
– x, y, z は 有限ビットで表現(上の桁が溢れて、無くなる)
z=x+y
if(z < x){ carry発生 }
– x,y どちらか片方のオペランドでのみ調べれば良い
– ただし、最下位ワードの場合のみ
– 上位ワードは、下からのcarry が来るので、これほど単純ではない
●
32bitアーキテクチャで、64bitまでは楽
●
下からのcarryが来ていれば、オペランドが(自己の値+carry)より小さい時carry(余計に条
件判断が必要)
●
多倍長演算にはCarryフラグが無いと辛い
●
ワードのMSB(最上位ビット)をCarryとして使うぐらいしか方法がない
– または、上記の(まどっろこしい)方法か、
●
Common Lispなどは無限長の整数(BigNum)がある
●
1990年ごろのMIPSは、BigNumが遅かった
overflowフラグを知っているか?
●
Overflowとは?
– UNIXマガジン 1991年12月 竹岡 ネタ
●
2の補数表示で、符号ビットにCarry/Borrowが入ってしまった状態
●
符号なし2進数の比較:
– 減算後Borrow (Carry) フラグを見る
●
2の補数の比較:
– 減算後の符号bitだけを見てもダメ
– 符号bitへBorrowが出ることがある==overflow
●
機械語の大小判断命令には符号付き/符号なしが存在
– 符号なし2進数用:Borrowのみ見る: Higher,Lower
– 2の補数用:Greater,Less (MIPSだと、Set on less than (slt))
●
2の補数の大小判断
– 減算結果の符号bit(Signフラグ) ^ Overflowフラグ →正しい符号
overflowフラグを知っているか?●
2の補数の大小判断
– 8bitの8080にはOverflowフラグが無し
●
当時のライバル6800にはoverflowフラグはあった。greater,less条件分
岐命令もあった
– 8080でも、オーバーフロー検出可能
– Cなどの高級言語で計算機のシミュレータを書くときoverflow検出は必要だ
ろう
●
2つのソース・オペランドの符号と演算結果の符号を比べる
– 加算:2つのソースの符号が一致 & 結果の符号がそれらと違う → オーバーフ
ロー
●
2つのソースの符号が一致していない時:結果はオーバーフローしない
– 減算の時は右オペランドの符号を反転させて考えれば、加算と同じ (A-
B==A+(-B) であるから)
●
どうでもいいが… Z80 にはOverflowフラグはあるが、2の補数用の条件判断命
令が無い
– 条件判断命令を組合せて2の補数の大小判断を行う
Aの符号 Bの符号  演算結果の
符号
 A+B の
OverFlow
  A-B の
OverFlow
+ + + N N
+ + − V N
+ − + N N
+ − − N V
− + + N V
− + − N N
− − + V N
Half Carryフラグを知っているか?
●
Half carry
– 下位4bit からの桁溢れ(Carry)
– BCD演算(補正)する時に、必要
●
BCD演算
– これは今どき、不要かな…と思わせて…
IBM zシリーズメインフレームは、 BCD演算機能、絶賛強化中
●
いつも、Half Carryを計算すると高コスト
●
BCD補正命令を、2進→16進ASCII文字変換に使用する
ADD AL, 90h ;ALの下位4bitを16進ASCII文字に変換(上位4bitは0であること)
DAA ;A>9 then 0x6を加算、即ち A上位4bit=0、Cy=1 else Cy=0
ADC AL, 40h ;A>9 then A'+0x40+1 else A'+0x40+0x90
DAA ;A>9 then 0x40+1+A else (A''==0xDX)→0x30+A
Parityフラグを知っているか?
●
Parity
– ワード内の全ビットのXOR結果
– これは今どき、不要かな…
– いつも、計算すると高コスト
CPUシミュレータ作り
●
いつも、計算すると高コスト
– Half Carry (Half Borrow)
– Parity
●
評価を遅延させるといいんじゃない
CPUシミュレータ作り
●
いつも、フラグの値を計算すると高コスト
– 参照されない値を作るのは、完全な無駄
●
フラグ計算を遅延できるの???
– UNIXマガジン 1992年6月 竹岡ネタ
–
●
条件判断などで、フラグを参照する時に、初めて値を作ればOK
●
最後に演算した結果から、即座に計算可能なフラグ
– Sign
– Parity
– Zero
– 最終演算結果のみ記憶しておけば、参照時にローコストに生成できる
●
Carryは、ワードの外にある
●
ある条件で、頻繁にクリアされたりする(特に8bit CPUでは)
●
最終演算結果のみからは、作れない
●
※ただし、単位ワードよりも1bitでも大きな記憶場所に、最終演算結果を保
持すれば、全く問題ない
●
割と参照頻度が高い
●
イーガ(即座)に作った方がいい(と思う。8bit CPUのシミュレートでは)
CPUシミュレータ作り
●
フラグの計算を遅延できるの???
●
条件判断などで、フラグを参照する時に、初めて値を作
ればOK
●
最終演算結果のみからは、計算が難しそうなフラグ
– Half Carry
– Overflow
●
ソース・オペランド2つを参照しないと、作れない
CPUシミュレータ作り
●
私の 8080 シミュレータ(1987年8月頃、Junetに投稿)
では…
●
Half Carryは、2つのソース・オペランドを、こっそり記憶
しておくことによって、
遅延評価を実現した
●
Half Carryを変化させる演算の2つのソース・オペラン
ドを、「隠れオペランド・コピー」へ、こっそり記憶
●
Half Carryが参照されたとき、「隠れオペランド・コ
ピー」を参照して、Half Carry値を計算する
●
Parityは、最終演算結果をコピーしておき、フラグを参照
されたとき、最終演算結果から、計算
「隠れオペランド・コピー」の、ちょっとした難しさ
●
フラグはスタックへpush/pop可能。
●
つまり、スタック上に、任意の値を作って、フラグへロードす
ることが可能
●
Half Carryフラグをスタックから取り出す時、
Half Carryフラグの値により、
適切な(もっともらしい)ソース・オペランドの値を、「隠れオ
ペランド・コピー」にセットする。
●
具体的には、「9」 を2つのオペランドにセットした。
●
これら「隠れオペランド・コピー」は、本当の演算には
使用しないので、問題無い
– Overflowも同様の考えでできる(はず)
●
Parityは、最終演算結果を記憶する変数を操作
– パリティが合い、SignやZeroと矛盾しないように
評価
●
実際に8080シミュレータでCP/Mを動かし
– そのもとでいくつかのアプリケーションを動かした(8bit 全盛期の 8bitマイコン用アプリケーション)
– コンパイラなど
●
実測値
– フラグをセットする命令数(s)  269939
– フラグを参照する命令数 (r)  192232
●
「フラグ捨て率」を定義
– フラグ捨て率 = フラグをセットする命令 vs フラグが参照されない
– a = (s-r)/s
●
今回 a = 28.9%
– 意外に少ないなと感じました。
– 8080,Z80 は、680x に比べるとフラグが変化する命令は少ない(近代的RISC風)
– 680x は、転送するだけで、フラグが変化する
●
人間が機械語で書いたプログラムはフラグを捨てる割合が高い傾向
– フラグ捨て率が40% 近く行くことがある
●
高級言語でかかれたプログラムは、フラグを参照する割合が高い
– フラグ捨て率は30%程度
●
約30%のフラグが捨てられているので、フラグの遅延評価は十分に効果を上げていると言える
●
※今日は、64bit Linuxでは動きません>それは tty ドライバのせいなんだよ… (c)えびじゅん
おしまい
アルバイト募集中
京都にて働きたい人も、若干名、募集中
AI (ルールに基づくやつ)づくり,機械学習つかい
OS,Hypervisor,コンパイラづくり
CPUシミュレータづくり

フラグ色々&その遅延評価-仮想機械インタープリタ書きのための-