about summary refs log tree commit diff stats
path: root/subx/014indirect_addressing.cc
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2019-03-12 18:56:55 -0700
committerKartik Agaram <vc@akkartik.com>2019-03-12 19:14:12 -0700
commit4a943d4ed313eff001504c2b5c472266e86a38af (patch)
treea5757233a8c81b303a808f251180c7344071ed51 /subx/014indirect_addressing.cc
parent43711b0e9f18e0225ce14687fb6ea0902aa6fc61 (diff)
downloadmu-4a943d4ed313eff001504c2b5c472266e86a38af.tar.gz
5001 - drop the :(scenario) DSL
I've been saying for a while[1][2][3] that adding extra abstractions makes
things harder for newcomers, and adding new notations doubly so. And then
I notice this DSL in my own backyard. Makes me feel like a hypocrite.

[1] https://news.ycombinator.com/item?id=13565743#13570092
[2] https://lobste.rs/s/to8wpr/configuration_files_are_canary_warning
[3] https://lobste.rs/s/mdmcdi/little_languages_by_jon_bentley_1986#c_3miuf2

The implementation of the DSL was also highly hacky:

a) It was happening in the tangle/ tool, but was utterly unrelated to tangling
layers.

b) There were several persnickety constraints on the different kinds of
lines and the specific order they were expected in. I kept finding bugs
where the translator would silently do the wrong thing. Or the error messages
sucked, and readers may be stuck looking at the generated code to figure
out what happened. Fixing error messages would require a lot more code,
which is one of my arguments against DSLs in the first place: they may
be easy to implement, but they're hard to design to go with the grain of
the underlying platform. They require lots of iteration. Is that effort
worth prioritizing in this project?

On the other hand, the DSL did make at least some readers' life easier,
the ones who weren't immediately put off by having to learn a strange syntax.
There were fewer quotes to parse, fewer backslash escapes.

Anyway, since there are also people who dislike having to put up with strange
syntaxes, we'll call that consideration a wash and tear this DSL out.

---

This commit was sheer drudgery. Hopefully it won't need to be redone with
a new DSL because I grow sick of backslashes.
Diffstat (limited to 'subx/014indirect_addressing.cc')
-rw-r--r--subx/014indirect_addressing.cc847
1 files changed, 505 insertions, 342 deletions
diff --git a/subx/014indirect_addressing.cc b/subx/014indirect_addressing.cc
index 448f4231..f591f0cf 100644
--- a/subx/014indirect_addressing.cc
+++ b/subx/014indirect_addressing.cc
@@ -1,18 +1,23 @@
 //: operating on memory at the address provided by some register
 //: we'll now start providing data in a separate segment
 
-:(scenario add_r32_to_mem_at_r32)
-% Reg[EBX].i = 0x10;
-% Reg[EAX].i = 0x2000;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  01  18                                     # add EBX to *EAX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-01 00 00 00  # 1
-+run: add EBX to r/m32
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0x00000011
+void test_add_r32_to_mem_at_r32() {
+  Reg[EBX].i = 0x10;
+  Reg[EAX].i = 0x2000;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  01  18                                       \n"  // add EBX to *EAX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "01 00 00 00\n"  // 0x00000001
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: add EBX to r/m32\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0x00000011\n"
+  );
+}
 
 :(before "End Mod Special-cases(addr)")
 case 0:  // indirect addressing
@@ -30,18 +35,24 @@ case 0:  // indirect addressing
 :(before "End Initialize Op Names")
 put_new(Name, "03", "add rm32 to r32 (add)");
 
-:(scenario add_mem_at_r32_to_r32)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0x10;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  03  18                                      # add *EAX to EBX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-01 00 00 00  # 1
-+run: add r/m32 to EBX
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0x00000011
+:(code)
+void test_add_mem_at_r32_to_r32() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0x10;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  03  18                                       \n"  // add *EAX to EBX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "01 00 00 00\n"  // 0x00000001
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: add r/m32 to EBX\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0x00000011\n"
+  );
+}
 
 :(before "End Single-Byte Opcodes")
 case 0x03: {  // add r/m32 to r32
@@ -55,36 +66,48 @@ case 0x03: {  // add r/m32 to r32
 
 //:: subtract
 
-:(scenario subtract_r32_from_mem_at_r32)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 1;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  29  18                                      # subtract EBX from *EAX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-0a 00 00 00  # 10
-+run: subtract EBX from r/m32
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0x00000009
+:(code)
+void test_subtract_r32_from_mem_at_r32() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 1;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  29  18                                       \n"  // subtract EBX from *EAX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "0a 00 00 00\n"  // 0x0000000a
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: subtract EBX from r/m32\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0x00000009\n"
+  );
+}
 
 //:
 
 :(before "End Initialize Op Names")
 put_new(Name, "2b", "subtract rm32 from r32 (sub)");
 
-:(scenario subtract_mem_at_r32_from_r32)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 10;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  2b  18                                      # subtract *EAX from EBX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-01 00 00 00  # 1
-+run: subtract r/m32 from EBX
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0x00000009
+:(code)
+void test_subtract_mem_at_r32_from_r32() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 10;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  2b     18                                    \n"  // subtract *EAX from EBX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "01 00 00 00\n"  // 0x00000001
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: subtract r/m32 from EBX\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0x00000009\n"
+  );
+}
 
 :(before "End Single-Byte Opcodes")
 case 0x2b: {  // subtract r/m32 from r32
@@ -97,37 +120,48 @@ case 0x2b: {  // subtract r/m32 from r32
 }
 
 //:: and
-
-:(scenario and_r32_with_mem_at_r32)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0xff;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  21  18                                      # and EBX with *EAX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-0d 0c 0b 0a  # 0x0a0b0c0d
-+run: and EBX with r/m32
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0x0000000d
+:(code)
+void test_and_r32_with_mem_at_r32() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0xff;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  21     18                                    \n"  // and EBX with *EAX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: and EBX with r/m32\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0x0000000d\n"
+  );
+}
 
 //:
 
 :(before "End Initialize Op Names")
 put_new(Name, "23", "r32 = bitwise AND of r32 with rm32 (and)");
 
-:(scenario and_mem_at_r32_with_r32)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0x0a0b0c0d;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  23  18                                      # and *EAX with EBX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-ff 00 00 00  # 0xff
-+run: and r/m32 with EBX
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0x0000000d
+:(code)
+void test_and_mem_at_r32_with_r32() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0x0a0b0c0d;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  23     18                                    \n"  // and *EAX with EBX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "ff 00 00 00\n"  // 0x000000ff
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: and r/m32 with EBX\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0x0000000d\n"
+  );
+}
 
 :(before "End Single-Byte Opcodes")
 case 0x23: {  // and r/m32 with r32
@@ -141,36 +175,48 @@ case 0x23: {  // and r/m32 with r32
 
 //:: or
 
-:(scenario or_r32_with_mem_at_r32)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0xa0b0c0d0;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  09  18                                      # or EBX with *EAX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-0d 0c 0b 0a  # 0x0a0b0c0d
-+run: or EBX with r/m32
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0xaabbccdd
+:(code)
+void test_or_r32_with_mem_at_r32() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0xa0b0c0d0;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  09  18                                      #\n"  // EBX with *EAX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: or EBX with r/m32\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0xaabbccdd\n"
+  );
+}
 
 //:
 
 :(before "End Initialize Op Names")
 put_new(Name, "0b", "r32 = bitwise OR of r32 with rm32 (or)");
 
-:(scenario or_mem_at_r32_with_r32)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0xa0b0c0d0;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  0b  18                                      # or *EAX with EBX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-0d 0c 0b 0a  # 0x0a0b0c0d
-+run: or r/m32 with EBX
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0xaabbccdd
+:(code)
+void test_or_mem_at_r32_with_r32() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0xa0b0c0d0;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  0b     18                                    \n"  // or *EAX with EBX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: or r/m32 with EBX\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0xaabbccdd\n"
+  );
+}
 
 :(before "End Single-Byte Opcodes")
 case 0x0b: {  // or r/m32 with r32
@@ -184,36 +230,47 @@ case 0x0b: {  // or r/m32 with r32
 
 //:: xor
 
-:(scenario xor_r32_with_mem_at_r32)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0xa0b0c0d0;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  31  18                                      # xor EBX with *EAX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-0d 0c bb aa  # 0xaabb0c0d
-+run: xor EBX with r/m32
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0x0a0bccdd
+:(code)
+void test_xor_r32_with_mem_at_r32() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0xa0b0c0d0;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  31     18                                    \n"  // xor EBX with *EAX
+      "== 0x2000\n"  // data segment
+      "0d 0c bb aa\n"  // 0xaabb0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: xor EBX with r/m32\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0x0a0bccdd\n"
+  );
+}
 
 //:
 
 :(before "End Initialize Op Names")
 put_new(Name, "33", "r32 = bitwise XOR of r32 with rm32 (xor)");
 
-:(scenario xor_mem_at_r32_with_r32)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0xa0b0c0d0;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  33  18                                      # xor *EAX with EBX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-0d 0c 0b 0a  # 0x0a0b0c0d
-+run: xor r/m32 with EBX
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0xaabbccdd
+:(code)
+void test_xor_mem_at_r32_with_r32() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0xa0b0c0d0;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  33     18                                    \n"  // xor *EAX with EBX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: xor r/m32 with EBX\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0xaabbccdd\n"
+  );
+}
 
 :(before "End Single-Byte Opcodes")
 case 0x33: {  // xor r/m32 with r32
@@ -227,77 +284,107 @@ case 0x33: {  // xor r/m32 with r32
 
 //:: not
 
-:(scenario not_of_mem_at_r32)
-% Reg[EBX].i = 0x2000;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  f7  13                                      # not *EBX
-# ModR/M in binary: 00 (indirect mode) 010 (subop not) 011 (dest EBX)
-== 0x2000  # data segment
-ff 00 0f 0f  # 0x0f0f00ff
-+run: operate on r/m32
-+run: effective address is 0x00002000 (EBX)
-+run: subop: not
-+run: storing 0xf0f0ff00
+:(code)
+void test_not_of_mem_at_r32() {
+  Reg[EBX].i = 0x2000;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  f7     13                                    \n"  // not *EBX
+      // ModR/M in binary: 00 (indirect mode) 010 (subop not) 011 (dest EBX)
+      "== 0x2000\n"  // data segment
+      "ff 00 0f 0f\n"  // 0x0f0f00ff
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: operate on r/m32\n"
+      "run: effective address is 0x00002000 (EBX)\n"
+      "run: subop: not\n"
+      "run: storing 0xf0f0ff00\n"
+  );
+}
 
 //:: compare (cmp)
 
-:(scenario compare_mem_at_r32_with_r32_greater)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0x0a0b0c07;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  39  18                                      # compare EBX with *EAX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-0d 0c 0b 0a  # 0x0a0b0c0d
-+run: compare EBX with r/m32
-+run: effective address is 0x00002000 (EAX)
-+run: SF=0; ZF=0; OF=0
-
-:(scenario compare_mem_at_r32_with_r32_lesser)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0x0a0b0c0d;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  39  18                                      # compare EBX with *EAX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-07 0c 0b 0a  # 0x0a0b0c0d
-+run: compare EBX with r/m32
-+run: effective address is 0x00002000 (EAX)
-+run: SF=1; ZF=0; OF=0
-
-:(scenario compare_mem_at_r32_with_r32_equal)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0x0a0b0c0d;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  39  18                                      # compare EBX with *EAX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-0d 0c 0b 0a  # 0x0a0b0c0d
-+run: compare EBX with r/m32
-+run: effective address is 0x00002000 (EAX)
-+run: SF=0; ZF=1; OF=0
+:(code)
+void test_compare_mem_at_r32_with_r32_greater() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0x0a0b0c07;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  39     18                                    \n"  // compare EBX with *EAX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: compare EBX with r/m32\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: SF=0; ZF=0; OF=0\n"
+  );
+}
+
+:(code)
+void test_compare_mem_at_r32_with_r32_lesser() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0x0a0b0c0d;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  39     18                                    \n"  // compare EBX with *EAX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "07 0c 0b 0a\n"  // 0x0a0b0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: compare EBX with r/m32\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: SF=1; ZF=0; OF=0\n"
+  );
+}
+
+:(code)
+void test_compare_mem_at_r32_with_r32_equal() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0x0a0b0c0d;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  39     18                                    \n"  // compare EBX with *EAX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: compare EBX with r/m32\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: SF=0; ZF=1; OF=0\n"
+  );
+}
 
 //:
 
 :(before "End Initialize Op Names")
 put_new(Name, "3b", "compare: set SF if r32 < rm32 (cmp)");
 
-:(scenario compare_r32_with_mem_at_r32_greater)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0x0a0b0c0d;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  3b  18                                      # compare *EAX with EBX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-07 0c 0b 0a  # 0x0a0b0c0d
-+run: compare r/m32 with EBX
-+run: effective address is 0x00002000 (EAX)
-+run: SF=0; ZF=0; OF=0
+:(code)
+void test_compare_r32_with_mem_at_r32_greater() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0x0a0b0c0d;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  3b     18                                    \n"  // compare *EAX with EBX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "07 0c 0b 0a\n"  // 0x0a0b0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: compare r/m32 with EBX\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: SF=0; ZF=0; OF=0\n"
+  );
+}
 
 :(before "End Single-Byte Opcodes")
 case 0x3b: {  // set SF if r32 < r/m32
@@ -315,61 +402,84 @@ case 0x3b: {  // set SF if r32 < r/m32
   break;
 }
 
-:(scenario compare_r32_with_mem_at_r32_lesser)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0x0a0b0c07;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  3b  18                                      # compare *EAX with EBX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-0d 0c 0b 0a  # 0x0a0b0c0d
-+run: compare r/m32 with EBX
-+run: effective address is 0x00002000 (EAX)
-+run: SF=1; ZF=0; OF=0
-
-:(scenario compare_r32_with_mem_at_r32_equal)
-% Reg[EAX].i = 0x2000;
-% Reg[EBX].i = 0x0a0b0c0d;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  3b  18                                      # compare *EAX with EBX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-== 0x2000  # data segment
-0d 0c 0b 0a  # 0x0a0b0c0d
-+run: compare r/m32 with EBX
-+run: effective address is 0x00002000 (EAX)
-+run: SF=0; ZF=1; OF=0
+:(code)
+void test_compare_r32_with_mem_at_r32_lesser() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0x0a0b0c07;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  3b     18                                    \n"  // compare *EAX with EBX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: compare r/m32 with EBX\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: SF=1; ZF=0; OF=0\n"
+  );
+}
+
+:(code)
+void test_compare_r32_with_mem_at_r32_equal() {
+  Reg[EAX].i = 0x2000;
+  Reg[EBX].i = 0x0a0b0c0d;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  3b     18                                    \n"  // compare *EAX with EBX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "0d 0c 0b 0a\n"  // 0x0a0b0c0d
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: compare r/m32 with EBX\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: SF=0; ZF=1; OF=0\n"
+  );
+}
 
 //:: copy (mov)
 
-:(scenario copy_r32_to_mem_at_r32)
-% Reg[EBX].i = 0xaf;
-% Reg[EAX].i = 0x60;
-== 0x1
-# op  ModR/M  SIB   displacement  immediate
-  89  18                                      # copy EBX to *EAX
-# ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
-+run: copy EBX to r/m32
-+run: effective address is 0x00000060 (EAX)
-+run: storing 0x000000af
+:(code)
+void test_copy_r32_to_mem_at_r32() {
+  Reg[EBX].i = 0xaf;
+  Reg[EAX].i = 0x60;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  89     18                                    \n"  // copy EBX to *EAX
+      // ModR/M in binary: 00 (indirect mode) 011 (src EAX) 000 (dest EAX)
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: copy EBX to r/m32\n"
+      "run: effective address is 0x00000060 (EAX)\n"
+      "run: storing 0x000000af\n"
+  );
+}
 
 //:
 
 :(before "End Initialize Op Names")
 put_new(Name, "8b", "copy rm32 to r32 (mov)");
 
-:(scenario copy_mem_at_r32_to_r32)
-% Reg[EAX].i = 0x2000;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  8b  18                                      # copy *EAX to EBX
-# ModR/M in binary: 00 (indirect mode) 011 (src EBX) 000 (dest EAX)
-== 0x2000  # data segment
-af 00 00 00  # 0xaf
-+run: copy r/m32 to EBX
-+run: effective address is 0x00002000 (EAX)
-+run: storing 0x000000af
+:(code)
+void test_copy_mem_at_r32_to_r32() {
+  Reg[EAX].i = 0x2000;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  8b     18                                    \n"  // copy *EAX to EBX
+      "== 0x2000\n"  // data segment
+      "af 00 00 00\n"  // 0x000000af
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: copy r/m32 to EBX\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: storing 0x000000af\n"
+  );
+}
 
 :(before "End Single-Byte Opcodes")
 case 0x8b: {  // copy r32 to r/m32
@@ -384,22 +494,28 @@ case 0x8b: {  // copy r32 to r/m32
 
 //:: jump
 
-:(scenario jump_mem_at_r32)
-% Reg[EAX].i = 0x2000;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  ff  20                                      # jump to *EAX
-# ModR/M in binary: 00 (indirect mode) 100 (jump to r/m32) 000 (src EAX)
-  05                              00 00 00 01
-  05                              00 00 00 02
-== 0x2000  # data segment
-08 00 00 00  # 8
-+run: 0x00000001 opcode: ff
-+run: jump to r/m32
-+run: effective address is 0x00002000 (EAX)
-+run: jumping to 0x00000008
-+run: 0x00000008 opcode: 05
--run: 0x00000003 opcode: 05
+:(code)
+void test_jump_mem_at_r32() {
+  Reg[EAX].i = 0x2000;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  ff     20                                    \n"  // jump to *EAX
+      // ModR/M in binary: 00 (indirect mode) 100 (jump to r/m32) 000 (src EAX)
+      "  05                                 00 00 00 01\n"
+      "  05                                 00 00 00 02\n"
+      "== 0x2000\n"  // data segment
+      "08 00 00 00\n"  // 0x00000008
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: 0x00000001 opcode: ff\n"
+      "run: jump to r/m32\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: jumping to 0x00000008\n"
+      "run: 0x00000008 opcode: 05\n"
+  );
+  CHECK_TRACE_DOESNT_CONTAIN("run: 0x00000003 opcode: 05");
+}
 
 :(before "End Op ff Subops")
 case 4: {  // jump to r/m32
@@ -412,19 +528,24 @@ case 4: {  // jump to r/m32
 
 //:: push
 
-:(scenario push_mem_at_r32)
-% Reg[EAX].i = 0x2000;
-% Reg[ESP].u = 0x14;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  ff  30                                      # push *EAX to stack
-# ModR/M in binary: 00 (indirect mode) 110 (push r/m32) 000 (src EAX)
-== 0x2000  # data segment
-af 00 00 00  # 0xaf
-+run: push r/m32
-+run: effective address is 0x00002000 (EAX)
-+run: decrementing ESP to 0x00000010
-+run: pushing value 0x000000af
+:(code)
+void test_push_mem_at_r32() {
+  Reg[EAX].i = 0x2000;
+  Reg[ESP].u = 0x14;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  ff     30                                    \n"  // push *EAX to stack
+      "== 0x2000\n"  // data segment
+      "af 00 00 00\n"  // 0x000000af
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: push r/m32\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+      "run: decrementing ESP to 0x00000010\n"
+      "run: pushing value 0x000000af\n"
+  );
+}
 
 :(before "End Op ff Subops")
 case 6: {  // push r/m32 to stack
@@ -439,19 +560,25 @@ case 6: {  // push r/m32 to stack
 :(before "End Initialize Op Names")
 put_new(Name, "8f", "pop top of stack to rm32 (pop)");
 
-:(scenario pop_mem_at_r32)
-% Reg[EAX].i = 0x60;
-% Reg[ESP].u = 0x2000;
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  8f  00                                      # pop stack into *EAX
-# ModR/M in binary: 00 (indirect mode) 000 (pop r/m32) 000 (dest EAX)
-== 0x2000  # data segment
-30 00 00 00  # 0x30
-+run: pop into r/m32
-+run: effective address is 0x00000060 (EAX)
-+run: popping value 0x00000030
-+run: incrementing ESP to 0x00002004
+:(code)
+void test_pop_mem_at_r32() {
+  Reg[EAX].i = 0x60;
+  Reg[ESP].u = 0x2000;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  8f     00                                    \n"  // pop stack into *EAX
+      // ModR/M in binary: 00 (indirect mode) 000 (pop r/m32) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "30 00 00 00\n"  // 0x00000030
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: pop into r/m32\n"
+      "run: effective address is 0x00000060 (EAX)\n"
+      "run: popping value 0x00000030\n"
+      "run: incrementing ESP to 0x00002004\n"
+  );
+}
 
 :(before "End Single-Byte Opcodes")
 case 0x8f: {  // pop stack into r/m32
@@ -470,17 +597,23 @@ case 0x8f: {  // pop stack into r/m32
 
 //:: special-case for loading address from disp32 rather than register
 
-:(scenario add_r32_to_mem_at_displacement)
-% Reg[EBX].i = 0x10;  // source
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  01  1d            00 20 00 00              # add EBX to *0x2000
-# ModR/M in binary: 00 (indirect mode) 011 (src EBX) 101 (dest in disp32)
-== 0x2000  # data segment
-01 00 00 00  # 1
-+run: add EBX to r/m32
-+run: effective address is 0x00002000 (disp32)
-+run: storing 0x00000011
+:(code)
+void test_add_r32_to_mem_at_displacement() {
+  Reg[EBX].i = 0x10;  // source
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  01     1d            00 20 00 00             \n"  // add EBX to *0x2000
+      // ModR/M in binary: 00 (indirect mode) 011 (src EBX) 101 (dest in disp32)
+      "== 0x2000\n"  // data segment
+      "01 00 00 00\n"  // 0x00000001
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: add EBX to r/m32\n"
+      "run: effective address is 0x00002000 (disp32)\n"
+      "run: storing 0x00000011\n"
+  );
+}
 
 :(before "End Mod 0 Special-cases(addr)")
 case 5:  // exception: mod 0b00 rm 0b101 => incoming disp32
@@ -490,19 +623,25 @@ case 5:  // exception: mod 0b00 rm 0b101 => incoming disp32
 
 //:
 
-:(scenario add_r32_to_mem_at_r32_plus_disp8)
-% Reg[EBX].i = 0x10;  // source
-% Reg[EAX].i = 0x1ffe;  // dest
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  01  58            02                       # add EBX to *(EAX+2)
-# ModR/M in binary: 01 (indirect+disp8 mode) 011 (src EBX) 000 (dest EAX)
-== 0x2000  # data segment
-01 00 00 00  # 1
-+run: add EBX to r/m32
-+run: effective address is initially 0x00001ffe (EAX)
-+run: effective address is 0x00002000 (after adding disp8)
-+run: storing 0x00000011
+:(code)
+void test_add_r32_to_mem_at_r32_plus_disp8() {
+  Reg[EBX].i = 0x10;  // source
+  Reg[EAX].i = 0x1ffe;  // dest
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  01     58            02                      \n"  // add EBX to *(EAX+2)
+      // ModR/M in binary: 01 (indirect+disp8 mode) 011 (src EBX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "01 00 00 00\n"  // 0x00000001
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: add EBX to r/m32\n"
+      "run: effective address is initially 0x00001ffe (EAX)\n"
+      "run: effective address is 0x00002000 (after adding disp8)\n"
+      "run: storing 0x00000011\n"
+  );
+}
 
 :(before "End Mod Special-cases(addr)")
 case 1:  // indirect + disp8 addressing
@@ -519,35 +658,47 @@ case 1:  // indirect + disp8 addressing
   }
   break;
 
-:(scenario add_r32_to_mem_at_r32_plus_negative_disp8)
-% Reg[EBX].i = 0x10;  // source
-% Reg[EAX].i = 0x2001;  // dest
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  01  58            ff                       # add EBX to *(EAX-1)
-# ModR/M in binary: 01 (indirect+disp8 mode) 011 (src EBX) 000 (dest EAX)
-== 0x2000  # data segment
-01 00 00 00  # 1
-+run: add EBX to r/m32
-+run: effective address is initially 0x00002001 (EAX)
-+run: effective address is 0x00002000 (after adding disp8)
-+run: storing 0x00000011
+:(code)
+void test_add_r32_to_mem_at_r32_plus_negative_disp8() {
+  Reg[EBX].i = 0x10;  // source
+  Reg[EAX].i = 0x2001;  // dest
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  01     58            ff                      \n"  // add EBX to *(EAX-1)
+      // ModR/M in binary: 01 (indirect+disp8 mode) 011 (src EBX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "01 00 00 00\n"  // 0x00000001
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: add EBX to r/m32\n"
+      "run: effective address is initially 0x00002001 (EAX)\n"
+      "run: effective address is 0x00002000 (after adding disp8)\n"
+      "run: storing 0x00000011\n"
+  );
+}
 
 //:
 
-:(scenario add_r32_to_mem_at_r32_plus_disp32)
-% Reg[EBX].i = 0x10;  // source
-% Reg[EAX].i = 0x1ffe;  // dest
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  01  98            02 00 00 00              # add EBX to *(EAX+2)
-# ModR/M in binary: 10 (indirect+disp32 mode) 011 (src EBX) 000 (dest EAX)
-== 0x2000  # data segment
-01 00 00 00  # 1
-+run: add EBX to r/m32
-+run: effective address is initially 0x00001ffe (EAX)
-+run: effective address is 0x00002000 (after adding disp32)
-+run: storing 0x00000011
+:(code)
+void test_add_r32_to_mem_at_r32_plus_disp32() {
+  Reg[EBX].i = 0x10;  // source
+  Reg[EAX].i = 0x1ffe;  // dest
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  01     98            02 00 00 00             \n"  // add EBX to *(EAX+2)
+      // ModR/M in binary: 10 (indirect+disp32 mode) 011 (src EBX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "01 00 00 00\n"  // 0x00000001
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: add EBX to r/m32\n"
+      "run: effective address is initially 0x00001ffe (EAX)\n"
+      "run: effective address is 0x00002000 (after adding disp32)\n"
+      "run: storing 0x00000011\n"
+  );
+}
 
 :(before "End Mod Special-cases(addr)")
 case 2:  // indirect + disp32 addressing
@@ -564,33 +715,45 @@ case 2:  // indirect + disp32 addressing
   }
   break;
 
-:(scenario add_r32_to_mem_at_r32_plus_negative_disp32)
-% Reg[EBX].i = 0x10;  // source
-% Reg[EAX].i = 0x2001;  // dest
-== 0x1  # code segment
-# op  ModR/M  SIB   displacement  immediate
-  01  98            ff ff ff ff              # add EBX to *(EAX-1)
-# ModR/M in binary: 10 (indirect+disp32 mode) 011 (src EBX) 000 (dest EAX)
-== 0x2000  # data segment
-01 00 00 00  # 1
-+run: add EBX to r/m32
-+run: effective address is initially 0x00002001 (EAX)
-+run: effective address is 0x00002000 (after adding disp32)
-+run: storing 0x00000011
+:(code)
+void test_add_r32_to_mem_at_r32_plus_negative_disp32() {
+  Reg[EBX].i = 0x10;  // source
+  Reg[EAX].i = 0x2001;  // dest
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  01     98            ff ff ff ff             \n"  // add EBX to *(EAX-1)
+      // ModR/M in binary: 10 (indirect+disp32 mode) 011 (src EBX) 000 (dest EAX)
+      "== 0x2000\n"  // data segment
+      "01 00 00 00\n"  // 0x00000001
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: add EBX to r/m32\n"
+      "run: effective address is initially 0x00002001 (EAX)\n"
+      "run: effective address is 0x00002000 (after adding disp32)\n"
+      "run: storing 0x00000011\n"
+  );
+}
 
 //:: copy address (lea)
 
 :(before "End Initialize Op Names")
 put_new(Name, "8d", "copy address in rm32 into r32 (lea)");
 
-:(scenario copy_address)
-% Reg[EAX].u = 0x2000;
-== 0x1
-# op  ModR/M  SIB   displacement  immediate
-  8d  18
-# ModR/M in binary: 00 (indirect mode) 011 (dest EBX) 000 (src EAX)
-+run: copy address into EBX
-+run: effective address is 0x00002000 (EAX)
+:(code)
+void test_copy_address() {
+  Reg[EAX].u = 0x2000;
+  run(
+      "== 0x1\n"  // code segment
+      // op     ModR/M  SIB   displacement  immediate
+      "  8d     18                                    \n"  // copy address in EAX into EBX
+      // ModR/M in binary: 00 (indirect mode) 011 (dest EBX) 000 (src EAX)
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: copy address into EBX\n"
+      "run: effective address is 0x00002000 (EAX)\n"
+  );
+}
 
 :(before "End Single-Byte Opcodes")
 case 0x8d: {  // copy address of m32 to r32