自作エミュレータで学ぶx86アーキテクチャ 3.10 cmp
https://github.com/mas454/x86emu
(define (cmp-r32-rm32 emu) (let ([modrm (make <modrm>)]) (parse-modrm emu modrm) (let* ([r32 (get-r32 emu modrm)] [rm32 (get-rm32 emu modrm)] [result (+ r32 (num32->2complement rm32))]) (update-eflags-sub emu r32 rm32 result)))) (define (cmp-rm32-imm8 emu modrm) (let ([rm32 (get-rm32 emu modrm)] [imm8 (get-code8 emu 0)]) (eip-add emu 1) (update-eflags-sub emu rm32 imm8 (+ rm32 (num32->2complement imm8)))))
cmp-r32-rm32とcmp-rm32-imm8は、cmp命令に対応した関数です。
cmpは比較のための命令で、内部では引き算を利用して比較を行っています。
元のコードでは、
uint64_t result = (uint64_t)r32 - (uint64_t)rm32;
のように減算の結果を符号なしの変数に入れているのですが、schemeだと無理なので
以下のように2の補数に変換してから加算で計算しています。
[result (+ r32 (num32->2complement rm32))])
num32-2complementが2の補数に変換する関数です。
(define (num32->2complement num) (+ (logxor num (- (expt 2 32) 1) ) 1))
単純に(+ (lognot num) 1)で計算するとマイナスになってしまうので、
上のプログラムのように計算しています。
update-eflags-subはフラグレジスタに値をセットする関数で、
フラグレジスタの値で条件分岐を行います。
(define (update-eflags-sub emu v1 v2 result) (let* ([sign1 (ash v1 -31)] [sign2 (ash v2 -31)] [signr (logand (ash result -31) 1)]) (set-carry emu (zero? (ash result -32))) (set-zero emu (zero? (copy-bit 32 result #f))) (set-sign emu (not (zero? signr))) (set-overflow emu (and (not (= sign1 sign2)) (not (= sign1 signr))))))
2の補数を使った計算を行っているので、キャリーフラグを1にする条件が元のコードとは
逆になっています。