about summary refs log tree commit diff stats
path: root/subx/013direct_addressing.cc
blob: b71468354165ffaa2b8053380923d4c8e9c0a3a8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<
" SubX syntax file
" Language:    SubX
" Maintainer:  Kartik Agaram <mu@akkartik.com>
" URL:         https://github.com/akkartik/mu
" License:     public domain
"
" Copy this into your ftplugin directory, and add the following to your vimrc
" or to .vim/ftdetect/subx.vim:
"   autocmd BufReadPost,BufNewFile *.subx set filetype=subx

let s:save_cpo = &cpo
set cpo&vim

" setlocal iskeyword=@,48-57,?,!,_,$,-
setlocal formatoptions-=t  " allow long lines
setlocal formatoptions+=c  " but comments should still wrap

setlocal iskeyword+=-

" blue tones
" comment colors for dark terminal: 14, 39, 27, 19
" comment colors for light terminal: 19, 27, 39, 6
"? syntax match subxH1Comment /# - .*/ | highlight subxH1Comment cterm=underline ctermfg=27
"? syntax match subxComment /#[^ ].*\|# [^.-].*\|# \?$/ | highlight subxComment ctermfg=27
"? syntax match subxS1Comment /# \..*/ | highlight subxS1Comment ctermfg=19
"? syntax match subxS2Comment /# \. \..*/ | highlight subxS2Comment ctermfg=245

" blue-green tones
syntax match subxH1Comment /# - .*/ | highlight subxH1Comment cterm=underline ctermfg=25
syntax match subxComment /#\( \.\| - \|? \)\@!.*/ | highlight subxComment ctermfg=25
syntax match subxS1Comment /# \..*/ | highlight subxS1Comment ctermfg=19
syntax match subxS2Comment /# \. \..*/ | highlight subxS2Comment ctermfg=245

" grey tones
"? syntax match subxH1Comment /# - .*/ | highlight subxH1Comment cterm=bold,underline
"? syntax match subxComment /#[^ ].*\|# [^.-].*\|# \?$/ | highlight subxComment cterm=bold ctermfg=236
"? hi Normal ctermfg=236
"? syntax match subxS1Comment /# \..*/ | highlight subxS1Comment cterm=bold ctermfg=242
"? syntax match subxS2Comment /# \. \..*/ | highlight subxS2Comment ctermfg=242

set comments-=:#
set comments+=n:#
syntax match subxCommentedCode "#? .*"  | highlight link subxCommentedCode CommentedCode
let b:cmt_head = "#? "

" comment token
syntax match subxDelimiter / \. /  | highlight link subxDelimiter Normal

syntax match subxString %"[^"]*"% | highlight link subxString Constant

"" definitions
" match globals but not registers like 'EAX'
" don't match capitalized words in metadata
" don't match inside strings
syntax match subxGlobal %\(/\)\@<!\<[A-Z][a-z0-9_-]*\>% | highlight link subxGlobal SpecialChar
" tweak the red color from the colorscheme just a tad to improve contrast
highlight SpecialChar ctermfg=160

" functions but not tests, globals or internal functions
syntax match subxFunction "^\(test_\)\@<![a-z][^ ]*\(:\)\@=" | highlight subxFunction cterm=underline ctermfg=130
" tests starting with 'test-'; dark:34 light:64
syntax match subxTest "^test-[^ ]*\(:\)\@=" | highlight subxTest ctermfg=64
" internal functions starting with '_'
syntax match subxMinorFunction "^_[^ ]*\(:\)\@=" | highlight subxMinorFunction ctermfg=95
" other internal labels starting with '$'
syntax match subxLabel "^\$[^ ]*\(:\)\@=" | highlight link subxLabel Constant

let &cpo = s:save_cpo
438'>438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
//: operating directly on a register

:(before "End Initialize Op Names")
put_new(Name, "01", "add r32 to rm32 (add)");

:(scenario add_r32_to_r32)
% Reg[EAX].i = 0x10;
% Reg[EBX].i = 1;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  01  d8                                      # add EBX to EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: add EBX to r/m32
+run: r/m32 is EAX
+run: storing 0x00000011

:(before "End Single-Byte Opcodes")
case 0x01: {  // add r32 to r/m32
  uint8_t modrm = next();
  uint8_t arg2 = (modrm>>3)&0x7;
  trace(90, "run") << "add " << rname(arg2) << " to r/m32" << end();
  int32_t* arg1 = effective_address(modrm);
  BINARY_ARITHMETIC_OP(+, *arg1, Reg[arg2].i);
  break;
}

:(code)
// Implement tables 2-2 and 2-3 in the Intel manual, Volume 2.
// We return a pointer so that instructions can write to multiple bytes in
// 'Mem' at once.
int32_t* effective_address(uint8_t modrm) {
  const uint8_t mod = (modrm>>6);
  // ignore middle 3 'reg opcode' bits
  const uint8_t rm = modrm & 0x7;
  if (mod == 3) {
    // mod 3 is just register direct addressing
    trace(90, "run") << "r/m32 is " << rname(rm) << end();
    return &Reg[rm].i;
  }
  return mem_addr_i32(effective_address_number(modrm));
}

uint32_t effective_address_number(uint8_t modrm) {
  const uint8_t mod = (modrm>>6);
  // ignore middle 3 'reg opcode' bits
  const uint8_t rm = modrm & 0x7;
  uint32_t addr = 0;
  switch (mod) {
  case 3:
    // mod 3 is just register direct addressing
    raise << "unexpected direct addressing mode\n" << end();
    return 0;
  // End Mod Special-cases(addr)
  default:
    cerr << "unrecognized mod bits: " << NUM(mod) << '\n';
    exit(1);
  }
  //: other mods are indirect, and they'll set addr appropriately
  // Found effective_address(addr)
  return addr;
}

string rname(uint8_t r) {
  switch (r) {
  case 0: return "EAX";
  case 1: return "ECX";
  case 2: return "EDX";
  case 3: return "EBX";
  case 4: return "ESP";
  case 5: return "EBP";
  case 6: return "ESI";
  case 7: return "EDI";
  default: raise << "invalid register " << r << '\n' << end();  return "";
  }
}

//:: subtract

:(before "End Initialize Op Names")
put_new(Name, "29", "subtract r32 from rm32 (sub)");

:(scenario subtract_r32_from_r32)
% Reg[EAX].i = 10;
% Reg[EBX].i = 1;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  29  d8                                      # subtract EBX from EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: subtract EBX from r/m32
+run: r/m32 is EAX
+run: storing 0x00000009

:(before "End Single-Byte Opcodes")
case 0x29: {  // subtract r32 from r/m32
  const uint8_t modrm = next();
  const uint8_t arg2 = (modrm>>3)&0x7;
  trace(90, "run") << "subtract " << rname(arg2) << " from r/m32" << end();
  int32_t* arg1 = effective_address(modrm);
  BINARY_ARITHMETIC_OP(-, *arg1, Reg[arg2].i);
  break;
}

//:: multiply

:(before "End Initialize Op Names")
put_new(Name, "f7", "negate/multiply rm32 (with EAX if necessary) depending on subop (neg/mul)");

:(scenario multiply_eax_by_r32)
% Reg[EAX].i = 4;
% Reg[ECX].i = 3;
== 0x1
# op      ModR/M  SIB   displacement  immediate
  f7      e1                                      # multiply EAX by ECX
# ModR/M in binary: 11 (direct mode) 100 (subop mul) 001 (src ECX)
+run: operate on r/m32
+run: r/m32 is ECX
+run: subop: multiply EAX by r/m32
+run: storing 0x0000000c

:(before "End Single-Byte Opcodes")
case 0xf7: {
  const uint8_t modrm = next();
  trace(90, "run") << "operate on r/m32" << end();
  int32_t* arg1 = effective_address(modrm);
  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
  switch (subop) {
  case 4: {  // mul unsigned EAX by r/m32
    trace(90, "run") << "subop: multiply EAX by r/m32" << end();
    const uint64_t result = Reg[EAX].u * static_cast<uint32_t>(*arg1);
    Reg[EAX].u = result & 0xffffffff;
    Reg[EDX].u = result >> 32;
    OF = (Reg[EDX].u != 0);
    trace(90, "run") << "storing 0x" << HEXWORD << Reg[EAX].u << end();
    break;
  }
  // End Op f7 Subops
  default:
    cerr << "unrecognized subop for opcode f7: " << NUM(subop) << '\n';
    exit(1);
  }
  break;
}

//:

:(before "End Initialize Op Names")
put_new(Name_0f, "af", "multiply rm32 into r32 (imul)");

:(scenario multiply_r32_into_r32)
% Reg[EAX].i = 4;
% Reg[EBX].i = 2;
== 0x1
# op      ModR/M  SIB   displacement  immediate
  0f af   d8                                      # subtract EBX into EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: multiply r/m32 into EBX
+run: r/m32 is EAX
+run: storing 0x00000008

:(before "End Two-Byte Opcodes Starting With 0f")
case 0xaf: {  // multiply r32 into r/m32
  const uint8_t modrm = next();
  const uint8_t arg2 = (modrm>>3)&0x7;
  trace(90, "run") << "multiply r/m32 into " << rname(arg2) << end();
  const int32_t* arg1 = effective_address(modrm);
  BINARY_ARITHMETIC_OP(*, Reg[arg2].i, *arg1);
  break;
}

//:: negate

:(scenario negate_r32)
% Reg[EBX].i = 1;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  f7  db                                      # negate EBX
# ModR/M in binary: 11 (direct mode) 011 (subop negate) 011 (dest EBX)
+run: operate on r/m32
+run: r/m32 is EBX
+run: subop: negate
+run: storing 0xffffffff

:(before "End Op f7 Subops")
case 3: {  // negate r/m32
  trace(90, "run") << "subop: negate" << end();
  // one case that can overflow
  if (static_cast<uint32_t>(*arg1) == 0x80000000) {
    trace(90, "run") << "overflow" << end();
    SF = true;
    ZF = false;
    OF = true;
    break;
  }
  *arg1 = -(*arg1);
  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
  SF = (*arg1 >> 31);
  ZF = (*arg1 == 0);
  OF = false;
  break;
}

:(scenario negate_can_overflow)  // in exactly one situation
% Reg[EBX].i = 0x80000000;  // INT_MIN
== 0x1
# op  ModR/M  SIB   displacement  immediate
  f7  db                                      # negate EBX
# ModR/M in binary: 11 (direct mode) 011 (subop negate) 011 (dest EBX)
+run: operate on r/m32
+run: r/m32 is EBX
+run: subop: negate
+run: overflow

//:: shift left

:(before "End Initialize Op Names")
put_new(Name, "d3", "shift rm32 by CL bits depending on subop (sal/sar/shl/shr)");

:(scenario shift_left_r32_with_cl)
% Reg[EBX].i = 13;
% Reg[ECX].i = 1;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  d3  e3                                      # negate EBX
# ModR/M in binary: 11 (direct mode) 100 (subop shift left) 011 (dest EBX)
+run: operate on r/m32
+run: r/m32 is EBX
+run: subop: shift left by CL bits
+run: storing 0x0000001a

:(before "End Single-Byte Opcodes")
case 0xd3: {
  const uint8_t modrm = next();
  trace(90, "run") << "operate on r/m32" << end();
  int32_t* arg1 = effective_address(modrm);
  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
  switch (subop) {
  case 4: {  // shift left r/m32 by CL
    trace(90, "run") << "subop: shift left by CL bits" << end();
    uint8_t count = Reg[ECX].u & 0x1f;
    // OF is only defined if count is 1
    if (count == 1) {
      bool msb = (*arg1 & 0x80000000) >> 1;
      bool pnsb = (*arg1 & 0x40000000);
      OF = (msb != pnsb);
    }
    *arg1 = (*arg1 << count);
    ZF = (*arg1 == 0);
    SF = (*arg1 < 0);
    trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
    break;
  }
  // End Op d3 Subops
  default:
    cerr << "unrecognized subop for opcode d3: " << NUM(subop) << '\n';
    exit(1);
  }
  break;
}

//:: shift right arithmetic

:(scenario shift_right_arithmetic_r32_with_cl)
% Reg[EBX].i = 26;
% Reg[ECX].i = 1;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  d3  fb                                      # negate EBX
# ModR/M in binary: 11 (direct mode) 111 (subop shift right arithmetic) 011 (dest EBX)
+run: operate on r/m32
+run: r/m32 is EBX
+run: subop: shift right by CL bits, while preserving sign
+run: storing 0x0000000d

:(before "End Op d3 Subops")
case 7: {  // shift right r/m32 by CL, preserving sign
  trace(90, "run") << "subop: shift right by CL bits, while preserving sign" << end();
  uint8_t count = Reg[ECX].u & 0x1f;
  *arg1 = (*arg1 >> count);
  ZF = (*arg1 == 0);
  SF = (*arg1 < 0);
  // OF is only defined if count is 1
  if (count == 1) OF = false;
  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
  break;
}

:(scenario shift_right_arithmetic_odd_r32_with_cl)
% Reg[EBX].i = 27;
% Reg[ECX].i = 1;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  d3  fb                                      # negate EBX
# ModR/M in binary: 11 (direct mode) 111 (subop shift right arithmetic) 011 (dest EBX)
+run: operate on r/m32
+run: r/m32 is EBX
+run: subop: shift right by CL bits, while preserving sign
# result: 13
+run: storing 0x0000000d

:(scenario shift_right_arithmetic_negative_r32_with_cl)
% Reg[EBX].i = 0xfffffffd;  // -3
% Reg[ECX].i = 1;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  d3  fb                                      # negate EBX
# ModR/M in binary: 11 (direct mode) 111 (subop shift right arithmetic) 011 (dest EBX)
+run: operate on r/m32
+run: r/m32 is EBX
+run: subop: shift right by CL bits, while preserving sign
# result: -2
+run: storing 0xfffffffe

//:: shift right logical

:(scenario shift_right_logical_r32_with_cl)
% Reg[EBX].i = 26;
% Reg[ECX].i = 1;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  d3  eb                                      # negate EBX
# ModR/M in binary: 11 (direct mode) 101 (subop shift right logical) 011 (dest EBX)
+run: operate on r/m32
+run: r/m32 is EBX
+run: subop: shift right by CL bits, while padding zeroes
+run: storing 0x0000000d

:(before "End Op d3 Subops")
case 5: {  // shift right r/m32 by CL, preserving sign
  trace(90, "run") << "subop: shift right by CL bits, while padding zeroes" << end();
  uint8_t count = Reg[ECX].u & 0x1f;
  // OF is only defined if count is 1
  if (count == 1) {
    bool msb = (*arg1 & 0x80000000) >> 1;
    bool pnsb = (*arg1 & 0x40000000);
    OF = (msb != pnsb);
  }
  uint32_t* uarg1 = reinterpret_cast<uint32_t*>(arg1);
  *uarg1 = (*uarg1 >> count);
  ZF = (*uarg1 == 0);
  // result is always positive by definition
  SF = false;
  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
  break;
}

:(scenario shift_right_logical_odd_r32_with_cl)
% Reg[EBX].i = 27;
% Reg[ECX].i = 1;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  d3  eb                                      # negate EBX
# ModR/M in binary: 11 (direct mode) 101 (subop shift right logical) 011 (dest EBX)
+run: operate on r/m32
+run: r/m32 is EBX
+run: subop: shift right by CL bits, while padding zeroes
# result: 13
+run: storing 0x0000000d

:(scenario shift_right_logical_negative_r32_with_cl)
% Reg[EBX].i = 0xfffffffd;
% Reg[ECX].i = 1;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  d3  eb                                      # negate EBX
# ModR/M in binary: 11 (direct mode) 101 (subop shift right logical) 011 (dest EBX)
+run: operate on r/m32
+run: r/m32 is EBX
+run: subop: shift right by CL bits, while padding zeroes
+run: storing 0x7ffffffe

//:: and

:(before "End Initialize Op Names")
put_new(Name, "21", "rm32 = bitwise AND of r32 with rm32 (and)");

:(scenario and_r32_with_r32)
% Reg[EAX].i = 0x0a0b0c0d;
% Reg[EBX].i = 0x000000ff;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  21  d8                                      # and EBX with destination EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: and EBX with r/m32
+run: r/m32 is EAX
+run: storing 0x0000000d

:(before "End Single-Byte Opcodes")
case 0x21: {  // and r32 with r/m32
  const uint8_t modrm = next();
  const uint8_t arg2 = (modrm>>3)&0x7;
  trace(90, "run") << "and " << rname(arg2) << " with r/m32" << end();
  int32_t* arg1 = effective_address(modrm);
  BINARY_BITWISE_OP(&, *arg1, Reg[arg2].u);
  break;
}

//:: or

:(before "End Initialize Op Names")
put_new(Name, "09", "rm32 = bitwise OR of r32 with rm32 (or)");

:(scenario or_r32_with_r32)
% Reg[EAX].i = 0x0a0b0c0d;
% Reg[EBX].i = 0xa0b0c0d0;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  09  d8                                      # or EBX with destination EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: or EBX with r/m32
+run: r/m32 is EAX
+run: storing 0xaabbccdd

:(before "End Single-Byte Opcodes")
case 0x09: {  // or r32 with r/m32
  const uint8_t modrm = next();
  const uint8_t arg2 = (modrm>>3)&0x7;
  trace(90, "run") << "or " << rname(arg2) << " with r/m32" << end();
  int32_t* arg1 = effective_address(modrm);
  BINARY_BITWISE_OP(|, *arg1, Reg[arg2].u);
  break;
}

//:: xor

:(before "End Initialize Op Names")
put_new(Name, "31", "rm32 = bitwise XOR of r32 with rm32 (xor)");

:(scenario xor_r32_with_r32)
% Reg[EAX].i = 0x0a0b0c0d;
% Reg[EBX].i = 0xaabbc0d0;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  31  d8                                      # xor EBX with destination EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: xor EBX with r/m32
+run: r/m32 is EAX
+run: storing 0xa0b0ccdd

:(before "End Single-Byte Opcodes")
case 0x31: {  // xor r32 with r/m32
  const uint8_t modrm = next();
  const uint8_t arg2 = (modrm>>3)&0x7;
  trace(90, "run") << "xor " << rname(arg2) << " with r/m32" << end();
  int32_t* arg1 = effective_address(modrm);
  BINARY_BITWISE_OP(^, *arg1, Reg[arg2].u);
  break;
}

//:: not

:(scenario not_r32)
% Reg[EBX].i = 0x0f0f00ff;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  f7  d3                                      # not EBX
# ModR/M in binary: 11 (direct mode) 010 (subop not) 011 (dest EBX)
+run: operate on r/m32
+run: r/m32 is EBX
+run: subop: not
+run: storing 0xf0f0ff00

:(before "End Op f7 Subops")
case 2: {  // not r/m32
  trace(90, "run") << "subop: not" << end();
  *arg1 = ~(*arg1);
  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << end();
  SF = (*arg1 >> 31);
  ZF = (*arg1 == 0);
  OF = false;
  break;
}

//:: compare (cmp)

:(before "End Initialize Op Names")
put_new(Name, "39", "compare: set SF if rm32 < r32 (cmp)");

:(scenario compare_r32_with_r32_greater)
% Reg[EAX].i = 0x0a0b0c0d;
% Reg[EBX].i = 0x0a0b0c07;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  39  d8                                      # compare EBX with EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: compare EBX with r/m32
+run: r/m32 is EAX
+run: SF=0; ZF=0; OF=0

:(before "End Single-Byte Opcodes")
case 0x39: {  // set SF if r/m32 < r32
  const uint8_t modrm = next();
  const uint8_t reg2 = (modrm>>3)&0x7;
  trace(90, "run") << "compare " << rname(reg2) << " with r/m32" << end();
  const int32_t* arg1 = effective_address(modrm);
  const int32_t arg2 = Reg[reg2].i;
  const int32_t tmp1 = *arg1 - arg2;
  SF = (tmp1 < 0);
  ZF = (tmp1 == 0);
  const int64_t tmp2 = *arg1 - arg2;
  OF = (tmp1 != tmp2);
  trace(90, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
  break;
}

:(scenario compare_r32_with_r32_lesser)
% Reg[EAX].i = 0x0a0b0c07;
% Reg[EBX].i = 0x0a0b0c0d;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  39  d8                                      # compare EBX with EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: compare EBX with r/m32
+run: r/m32 is EAX
+run: SF=1; ZF=0; OF=0

:(scenario compare_r32_with_r32_equal)
% Reg[EAX].i = 0x0a0b0c0d;
% Reg[EBX].i = 0x0a0b0c0d;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  39  d8                                      # compare EBX with EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: compare EBX with r/m32
+run: r/m32 is EAX
+run: SF=0; ZF=1; OF=0

//:: copy (mov)

:(before "End Initialize Op Names")
put_new(Name, "89", "copy r32 to rm32 (mov)");

:(scenario copy_r32_to_r32)
% Reg[EBX].i = 0xaf;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  89  d8                                      # copy EBX to EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: copy EBX to r/m32
+run: r/m32 is EAX
+run: storing 0x000000af

:(before "End Single-Byte Opcodes")
case 0x89: {  // copy r32 to r/m32
  const uint8_t modrm = next();
  const uint8_t rsrc = (modrm>>3)&0x7;
  trace(90, "run") << "copy " << rname(rsrc) << " to r/m32" << end();
  int32_t* dest = effective_address(modrm);
  *dest = Reg[rsrc].i;
  trace(90, "run") << "storing 0x" << HEXWORD << *dest << end();
  break;
}

//:: xchg

:(before "End Initialize Op Names")
put_new(Name, "87", "swap the contents of r32 and rm32 (xchg)");

:(scenario xchg_r32_with_r32)
% Reg[EBX].i = 0xaf;
% Reg[EAX].i = 0x2e;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  87  d8                                      # exchange EBX with EAX
# ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
+run: exchange EBX with r/m32
+run: r/m32 is EAX
+run: storing 0x000000af in r/m32
+run: storing 0x0000002e in EBX

:(before "End Single-Byte Opcodes")
case 0x87: {  // exchange r32 with r/m32
  const uint8_t modrm = next();
  const uint8_t reg2 = (modrm>>3)&0x7;
  trace(90, "run") << "exchange " << rname(reg2) << " with r/m32" << end();
  int32_t* arg1 = effective_address(modrm);
  const int32_t tmp = *arg1;
  *arg1 = Reg[reg2].i;
  Reg[reg2].i = tmp;
  trace(90, "run") << "storing 0x" << HEXWORD << *arg1 << " in r/m32" << end();
  trace(90, "run") << "storing 0x" << HEXWORD << Reg[reg2].i << " in " << rname(reg2) << end();
  break;
}

//:: increment

:(before "End Initialize Op Names")
put_new(Name, "40", "increment EAX (inc)");
put_new(Name, "41", "increment ECX (inc)");
put_new(Name, "42", "increment EDX (inc)");
put_new(Name, "43", "increment EBX (inc)");
put_new(Name, "44", "increment ESP (inc)");
put_new(Name, "45", "increment EBP (inc)");
put_new(Name, "46", "increment ESI (inc)");
put_new(Name, "47", "increment EDI (inc)");

:(scenario increment_r32)
% Reg[ECX].u = 0x1f;
== 0x1  # code segment
# op  ModR/M  SIB   displacement  immediate
  41                                          # increment ECX
+run: increment ECX
+run: storing value 0x00000020

:(before "End Single-Byte Opcodes")
case 0x40:
case 0x41:
case 0x42:
case 0x43:
case 0x44:
case 0x45:
case 0x46:
case 0x47: {  // increment r32
  const uint8_t reg = op & 0x7;
  trace(90, "run") << "increment " << rname(reg) << end();
  ++Reg[reg].u;
  trace(90, "run") << "storing value 0x" << HEXWORD << Reg[reg].u << end();
  break;
}

:(before "End Initialize Op Names")
put_new(Name, "ff", "increment/decrement/jump/push/call rm32 based on subop (inc/dec/jmp/push/call)");

:(scenario increment_rm32)
% Reg[EAX].u = 0x20;
== 0x1  # code segment
# op  ModR/M  SIB   displacement  immediate
  ff  c0                                      # increment EAX
# ModR/M in binary: 11 (direct mode) 000 (subop inc) 000 (EAX)
+run: increment r/m32
+run: r/m32 is EAX
+run: storing value 0x00000021

:(before "End Single-Byte Opcodes")
case 0xff: {
  const uint8_t modrm = next();
  const uint8_t subop = (modrm>>3)&0x7;  // middle 3 'reg opcode' bits
  switch (subop) {
    case 0: {  // increment r/m32
      trace(90, "run") << "increment r/m32" << end();
      int32_t* arg = effective_address(modrm);
      ++*arg;
      trace(90, "run") << "storing value 0x" << HEXWORD << *arg << end();
      break;
    }
    default:
      cerr << "unrecognized subop for ff: " << HEXBYTE << NUM(subop) << '\n';
      DUMP("");
      exit(1);
    // End Op ff Subops
  }
  break;
}

//:: decrement

:(before "End Initialize Op Names")
put_new(Name, "48", "decrement EAX (dec)");
put_new(Name, "49", "decrement ECX (dec)");
put_new(Name, "4a", "decrement EDX (dec)");
put_new(Name, "4b", "decrement EBX (dec)");
put_new(Name, "4c", "decrement ESP (dec)");
put_new(Name, "4d", "decrement EBP (dec)");
put_new(Name, "4e", "decrement ESI (dec)");
put_new(Name, "4f", "decrement EDI (dec)");

:(scenario decrement_r32)
% Reg[ECX].u = 0x1f;
== 0x1  # code segment
# op  ModR/M  SIB   displacement  immediate
  49                                          # decrement ECX
+run: decrement ECX
+run: storing value 0x0000001e

:(before "End Single-Byte Opcodes")
case 0x48:
case 0x49:
case 0x4a:
case 0x4b:
case 0x4c:
case 0x4d:
case 0x4e:
case 0x4f: {  // decrement r32
  const uint8_t reg = op & 0x7;
  trace(90, "run") << "decrement " << rname(reg) << end();
  --Reg[reg].u;
  trace(90, "run") << "storing value 0x" << HEXWORD << Reg[reg].u << end();
  break;
}

:(scenario decrement_rm32)
% Reg[EAX].u = 0x20;
== 0x1  # code segment
# op  ModR/M  SIB   displacement  immediate
  ff  c8                                      # decrement EAX
# ModR/M in binary: 11 (direct mode) 001 (subop inc) 000 (EAX)
+run: decrement r/m32
+run: r/m32 is EAX
+run: storing value 0x0000001f

:(before "End Op ff Subops")
case 1: {  // decrement r/m32
  trace(90, "run") << "decrement r/m32" << end();
  int32_t* arg = effective_address(modrm);
  --*arg;
  trace(90, "run") << "storing value 0x" << HEXWORD << *arg << end();
  break;
}

//:: push

:(before "End Initialize Op Names")
put_new(Name, "50", "push EAX to stack (push)");
put_new(Name, "51", "push ECX to stack (push)");
put_new(Name, "52", "push EDX to stack (push)");
put_new(Name, "53", "push EBX to stack (push)");
put_new(Name, "54", "push ESP to stack (push)");
put_new(Name, "55", "push EBP to stack (push)");
put_new(Name, "56", "push ESI to stack (push)");
put_new(Name, "57", "push EDI to stack (push)");

:(scenario push_r32)
% Reg[ESP].u = 0x64;
% Reg[EBX].i = 0x0000000a;
== 0x1
# op  ModR/M  SIB   displacement  immediate
  53                                          # push EBX to stack
+run: push EBX
+run: decrementing ESP to 0x00000060
+run: pushing value 0x0000000a

:(before "End Single-Byte Opcodes")
case 0x50:
case 0x51:
case 0x52:
case 0x53:
case 0x54:
case 0x55:
case 0x56:
case 0x57: {  // push r32 to stack
  uint8_t reg = op & 0x7;
  trace(90, "run") << "push " << rname(reg) << end();
//?   cerr << "push: " << NUM(reg) << ": " << Reg[reg].u << " => " << Reg[ESP].u << '\n';
  push(Reg[reg].u);
  break;
}

//:: pop

:(before "End Initialize Op Names")
put_new(Name, "58", "pop top of stack to EAX (pop)");
put_new(Name, "59", "pop top of stack to ECX (pop)");
put_new(Name, "5a", "pop top of stack to EDX (pop)");
put_new(Name, "5b", "pop top of stack to EBX (pop)");
put_new(Name, "5c", "pop top of stack to ESP (pop)");
put_new(Name, "5d", "pop top of stack to EBP (pop)");
put_new(Name, "5e", "pop top of stack to ESI (pop)");
put_new(Name, "5f", "pop top of stack to EDI (pop)");

:(scenario pop_r32)
% Reg[ESP].u = 0x02000000;
% Mem.push_back(vma(0x02000000));  // manually allocate memory
% write_mem_i32(0x02000000, 0x0000000a);  // ..before this write
== 0x1  # code segment
# op  ModR/M  SIB   displacement  immediate
  5b                                          # pop stack to EBX
== 0x2000  # data segment
0a 00 00 00  # 0x0a
+run: pop into EBX
+run: popping value 0x0000000a
+run: incrementing ESP to 0x02000004

:(before "End Single-Byte Opcodes")
case 0x58:
case 0x59:
case 0x5a:
case 0x5b:
case 0x5c:
case 0x5d:
case 0x5e:
case 0x5f: {  // pop stack into r32
  const uint8_t reg = op & 0x7;
  trace(90, "run") << "pop into " << rname(reg) << end();
//?   cerr << "pop from " << Reg[ESP].u << '\n';
  Reg[reg].u = pop();
//?   cerr << "=> " << NUM(reg) << ": " << Reg[reg].u << '\n';
  break;
}
:(code)
uint32_t pop() {
  const uint32_t result = read_mem_u32(Reg[ESP].u);
  trace(90, "run") << "popping value 0x" << HEXWORD << result << end();
  Reg[ESP].u += 4;
  trace(90, "run") << "incrementing ESP to 0x" << HEXWORD << Reg[ESP].u << end();
  return result;
}