自作エミュレータで学ぶ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にする条件が元のコードとは
逆になっています。