about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rwxr-xr-xapps/mubin102349 -> 102357 bytes
-rw-r--r--apps/mu.subx18
-rw-r--r--html/apps/mu.subx.html18
-rw-r--r--mu_instructions186
-rw-r--r--subx_opcodes (renamed from opcodes)0
-rw-r--r--vimrc.vim4
7 files changed, 209 insertions, 22 deletions
diff --git a/README.md b/README.md
index b9ebf52c..d0bb5992 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,8 @@ statements in Mu translate to a single machine code instruction. Variables
 reside in memory by default. Programs must specify registers when they want to
 use them. Functions must return results in registers. Execution begins at the
 function `main`, which always returns its result in register `ebx`. [This post](http://akkartik.name/post/mu-2019-2)
-has more details.
+has more details. You can see a complete list of supported instructions and
+their translations in [this summary](mu_instructions).
 
 ## SubX
 
@@ -717,7 +718,7 @@ a) Try running the tests: `./test_apps`
 b) Check out the online help. Starting point: `./bootstrap`
 
 c) Familiarize yourself with `./bootstrap help opcodes`. If you program in Mu
-you'll spend a lot of time with it. (It's also [in this repo](https://github.com/akkartik/mu/blob/master/opcodes).)
+you'll spend a lot of time with it. (It's also [in this repo](https://github.com/akkartik/mu/blob/master/subx_opcodes).)
 [Here](https://lobste.rs/s/qglfdp/subx_minimalist_assembly_language_for#c_o9ddqk)
 are some tips on my setup for quickly finding the right opcode for any
 situation from within Vim.
diff --git a/apps/mu b/apps/mu
index c4f8dbaf..da654f63 100755
--- a/apps/mu
+++ b/apps/mu
Binary files differdiff --git a/apps/mu.subx b/apps/mu.subx
index c7c972ad..ad297dd2 100644
--- a/apps/mu.subx
+++ b/apps/mu.subx
@@ -5048,7 +5048,7 @@ _Primitive-or-lit-with-reg:
     "or"/imm32/name
     Single-lit-var/imm32/inouts
     Single-int-var-in-some-register/imm32/outputs
-    "81 4/subop/or"/imm32/subx-name
+    "81 1/subop/or"/imm32/subx-name
     3/imm32/rm32-is-first-output
     0/imm32/no-r32
     1/imm32/imm32-is-first-inout
@@ -5060,7 +5060,7 @@ _Primitive-or-lit-with-mem:
     "or-with"/imm32/name
     Int-var-and-literal/imm32/inouts
     0/imm32/outputs
-    "81 4/subop/or"/imm32/subx-name
+    "81 1/subop/or"/imm32/subx-name
     1/imm32/rm32-is-first-inout
     0/imm32/no-r32
     2/imm32/imm32-is-second-inout
@@ -5121,7 +5121,7 @@ _Primitive-xor-lit-with-reg:
     "xor"/imm32/name
     Single-lit-var/imm32/inouts
     Single-int-var-in-some-register/imm32/outputs
-    "81 4/subop/xor"/imm32/subx-name
+    "81 6/subop/xor"/imm32/subx-name
     3/imm32/rm32-is-first-output
     0/imm32/no-r32
     1/imm32/imm32-is-first-inout
@@ -5133,7 +5133,7 @@ _Primitive-xor-lit-with-mem:
     "xor-with"/imm32/name
     Int-var-and-literal/imm32/inouts
     0/imm32/outputs
-    "81 4/subop/xor"/imm32/subx-name
+    "81 6/subop/xor"/imm32/subx-name
     1/imm32/rm32-is-first-inout
     0/imm32/no-r32
     2/imm32/imm32-is-first-inout
@@ -5279,7 +5279,7 @@ _Primitive-compare-mem-with-reg:
     "compare"/imm32/name
     Two-args-int-stack-int-reg/imm32/inouts
     0/imm32/outputs
-    "39/compare"/imm32/subx-name
+    "39/compare->"/imm32/subx-name
     1/imm32/rm32-is-first-inout
     2/imm32/r32-is-second-inout
     0/imm32/no-imm32
@@ -5287,11 +5287,11 @@ _Primitive-compare-mem-with-reg:
     0/imm32/output-is-write-only
     _Primitive-compare-reg-with-mem/imm32/next
 _Primitive-compare-reg-with-mem:
-    # compare var1/reg var2 => 3b/compare-> var2/rm32 var1/r32
+    # compare var1/reg var2 => 3b/compare<- var2/rm32 var1/r32
     "compare"/imm32/name
     Two-args-int-reg-int-stack/imm32/inouts
     0/imm32/outputs
-    "3b/compare"/imm32/subx-name
+    "3b/compare<-"/imm32/subx-name
     2/imm32/rm32-is-second-inout
     1/imm32/r32-is-first-inout
     0/imm32/no-imm32
@@ -7454,7 +7454,7 @@ test-compare-mem-with-reg:
 #?     (rewind-stream _test-output-stream)
 #?     # }}}
     # check output
-    (check-next-stream-line-equal _test-output-stream "39/compare *(ebp+0x00000008) 0x00000000/r32" "F - test-compare-mem-with-reg")
+    (check-next-stream-line-equal _test-output-stream "39/compare-> *(ebp+0x00000008) 0x00000000/r32" "F - test-compare-mem-with-reg")
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
@@ -7513,7 +7513,7 @@ test-compare-reg-with-mem:
 #?     (rewind-stream _test-output-stream)
 #?     # }}}
     # check output
-    (check-next-stream-line-equal _test-output-stream "3b/compare *(ebp+0x00000008) 0x00000000/r32" "F - test-compare-reg-with-mem")
+    (check-next-stream-line-equal _test-output-stream "3b/compare<- *(ebp+0x00000008) 0x00000000/r32" "F - test-compare-reg-with-mem")
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
diff --git a/html/apps/mu.subx.html b/html/apps/mu.subx.html
index 4c1682e2..8ac2db8e 100644
--- a/html/apps/mu.subx.html
+++ b/html/apps/mu.subx.html
@@ -5030,7 +5030,7 @@ if ('onhashchange' in window) {
 <span id="L5048" class="LineNr">5048 </span>    <span class="Constant">&quot;or&quot;</span>/imm32/name
 <span id="L5049" class="LineNr">5049 </span>    <span class="SpecialChar"><a href='mu.subx.html#L5907'>Single-lit-var</a></span>/imm32/inouts
 <span id="L5050" class="LineNr">5050 </span>    <span class="SpecialChar"><a href='mu.subx.html#L5830'>Single-int-var-in-some-register</a></span>/imm32/outputs
-<span id="L5051" class="LineNr">5051 </span>    <span class="Constant">&quot;81 4/subop/or&quot;</span>/imm32/subx-name
+<span id="L5051" class="LineNr">5051 </span>    <span class="Constant">&quot;81 1/subop/or&quot;</span>/imm32/subx-name
 <span id="L5052" class="LineNr">5052 </span>    3/imm32/rm32-is-first-output
 <span id="L5053" class="LineNr">5053 </span>    0/imm32/no-r32
 <span id="L5054" class="LineNr">5054 </span>    1/imm32/imm32-is-first-inout
@@ -5042,7 +5042,7 @@ if ('onhashchange' in window) {
 <span id="L5060" class="LineNr">5060 </span>    <span class="Constant">&quot;or-with&quot;</span>/imm32/name
 <span id="L5061" class="LineNr">5061 </span>    <span class="SpecialChar"><a href='mu.subx.html#L5826'>Int-var-and-literal</a></span>/imm32/inouts
 <span id="L5062" class="LineNr">5062 </span>    0/imm32/outputs
-<span id="L5063" class="LineNr">5063 </span>    <span class="Constant">&quot;81 4/subop/or&quot;</span>/imm32/subx-name
+<span id="L5063" class="LineNr">5063 </span>    <span class="Constant">&quot;81 1/subop/or&quot;</span>/imm32/subx-name
 <span id="L5064" class="LineNr">5064 </span>    1/imm32/rm32-is-first-inout
 <span id="L5065" class="LineNr">5065 </span>    0/imm32/no-r32
 <span id="L5066" class="LineNr">5066 </span>    2/imm32/imm32-is-second-inout
@@ -5103,7 +5103,7 @@ if ('onhashchange' in window) {
 <span id="L5121" class="LineNr">5121 </span>    <span class="Constant">&quot;xor&quot;</span>/imm32/name
 <span id="L5122" class="LineNr">5122 </span>    <span class="SpecialChar"><a href='mu.subx.html#L5907'>Single-lit-var</a></span>/imm32/inouts
 <span id="L5123" class="LineNr">5123 </span>    <span class="SpecialChar"><a href='mu.subx.html#L5830'>Single-int-var-in-some-register</a></span>/imm32/outputs
-<span id="L5124" class="LineNr">5124 </span>    <span class="Constant">&quot;81 4/subop/xor&quot;</span>/imm32/subx-name
+<span id="L5124" class="LineNr">5124 </span>    <span class="Constant">&quot;81 6/subop/xor&quot;</span>/imm32/subx-name
 <span id="L5125" class="LineNr">5125 </span>    3/imm32/rm32-is-first-output
 <span id="L5126" class="LineNr">5126 </span>    0/imm32/no-r32
 <span id="L5127" class="LineNr">5127 </span>    1/imm32/imm32-is-first-inout
@@ -5115,7 +5115,7 @@ if ('onhashchange' in window) {
 <span id="L5133" class="LineNr">5133 </span>    <span class="Constant">&quot;xor-with&quot;</span>/imm32/name
 <span id="L5134" class="LineNr">5134 </span>    <span class="SpecialChar"><a href='mu.subx.html#L5826'>Int-var-and-literal</a></span>/imm32/inouts
 <span id="L5135" class="LineNr">5135 </span>    0/imm32/outputs
-<span id="L5136" class="LineNr">5136 </span>    <span class="Constant">&quot;81 4/subop/xor&quot;</span>/imm32/subx-name
+<span id="L5136" class="LineNr">5136 </span>    <span class="Constant">&quot;81 6/subop/xor&quot;</span>/imm32/subx-name
 <span id="L5137" class="LineNr">5137 </span>    1/imm32/rm32-is-first-inout
 <span id="L5138" class="LineNr">5138 </span>    0/imm32/no-r32
 <span id="L5139" class="LineNr">5139 </span>    2/imm32/imm32-is-first-inout
@@ -5261,7 +5261,7 @@ if ('onhashchange' in window) {
 <span id="L5279" class="LineNr">5279 </span>    <span class="Constant">&quot;compare&quot;</span>/imm32/name
 <span id="L5280" class="LineNr">5280 </span>    <span class="SpecialChar"><a href='mu.subx.html#L5814'>Two-args-int-stack-int-reg</a></span>/imm32/inouts
 <span id="L5281" class="LineNr">5281 </span>    0/imm32/outputs
-<span id="L5282" class="LineNr">5282 </span>    <span class="Constant">&quot;39/compare&quot;</span>/imm32/subx-name
+<span id="L5282" class="LineNr">5282 </span>    <span class="Constant">&quot;39/compare-&gt;&quot;</span>/imm32/subx-name
 <span id="L5283" class="LineNr">5283 </span>    1/imm32/rm32-is-first-inout
 <span id="L5284" class="LineNr">5284 </span>    2/imm32/r32-is-second-inout
 <span id="L5285" class="LineNr">5285 </span>    0/imm32/no-imm32
@@ -5269,11 +5269,11 @@ if ('onhashchange' in window) {
 <span id="L5287" class="LineNr">5287 </span>    0/imm32/output-is-write-only
 <span id="L5288" class="LineNr">5288 </span>    <a href='mu.subx.html#L5289'>_Primitive-compare-reg-with-mem</a>/imm32/next
 <span id="L5289" class="LineNr">5289 </span><span class="subxMinorFunction">_Primitive-compare-reg-with-mem</span>:
-<span id="L5290" class="LineNr">5290 </span>    <span class="subxComment"># compare var1/reg var2 =&gt; 3b/compare-&gt; var2/rm32 var1/r32</span>
+<span id="L5290" class="LineNr">5290 </span>    <span class="subxComment"># compare var1/reg var2 =&gt; 3b/compare&lt;- var2/rm32 var1/r32</span>
 <span id="L5291" class="LineNr">5291 </span>    <span class="Constant">&quot;compare&quot;</span>/imm32/name
 <span id="L5292" class="LineNr">5292 </span>    <span class="SpecialChar"><a href='mu.subx.html#L5818'>Two-args-int-reg-int-stack</a></span>/imm32/inouts
 <span id="L5293" class="LineNr">5293 </span>    0/imm32/outputs
-<span id="L5294" class="LineNr">5294 </span>    <span class="Constant">&quot;3b/compare&quot;</span>/imm32/subx-name
+<span id="L5294" class="LineNr">5294 </span>    <span class="Constant">&quot;3b/compare&lt;-&quot;</span>/imm32/subx-name
 <span id="L5295" class="LineNr">5295 </span>    2/imm32/rm32-is-second-inout
 <span id="L5296" class="LineNr">5296 </span>    1/imm32/r32-is-first-inout
 <span id="L5297" class="LineNr">5297 </span>    0/imm32/no-imm32
@@ -7371,7 +7371,7 @@ if ('onhashchange' in window) {
 <span id="L7449" class="LineNr">7449 </span>    (<a href='../064write-byte.subx.html#L81'>flush</a> <a href='../064write-byte.subx.html#L328'>_test-output-buffered-file</a>)
 <span id="L7450" class="Folded">7450 </span><span class="Folded">+--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------</span>
 <span id="L7456" class="LineNr">7456 </span>    <span class="subxComment"># check output</span>
-<span id="L7457" class="LineNr">7457 </span>    (<a href='../058stream-equal.subx.html#L565'>check-next-stream-line-equal</a> <a href='../064write-byte.subx.html#L286'>_test-output-stream</a> <span class="Constant">&quot;39/compare *(ebp+0x00000008) 0x00000000/r32&quot;</span> <span class="Constant">&quot;F - test-compare-mem-with-reg&quot;</span>)
+<span id="L7457" class="LineNr">7457 </span>    (<a href='../058stream-equal.subx.html#L565'>check-next-stream-line-equal</a> <a href='../064write-byte.subx.html#L286'>_test-output-stream</a> <span class="Constant">&quot;39/compare-&gt; *(ebp+0x00000008) 0x00000000/r32&quot;</span> <span class="Constant">&quot;F - test-compare-mem-with-reg&quot;</span>)
 <span id="L7458" class="LineNr">7458 </span>    <span class="subxS1Comment"># . epilogue</span>
 <span id="L7459" class="LineNr">7459 </span>    89/&lt;- %esp 5/r32/ebp
 <span id="L7460" class="LineNr">7460 </span>    5d/pop-to-ebp
@@ -7425,7 +7425,7 @@ if ('onhashchange' in window) {
 <span id="L7508" class="LineNr">7508 </span>    (<a href='../064write-byte.subx.html#L81'>flush</a> <a href='../064write-byte.subx.html#L328'>_test-output-buffered-file</a>)
 <span id="L7509" class="Folded">7509 </span><span class="Folded">+--  6 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------</span>
 <span id="L7515" class="LineNr">7515 </span>    <span class="subxComment"># check output</span>
-<span id="L7516" class="LineNr">7516 </span>    (<a href='../058stream-equal.subx.html#L565'>check-next-stream-line-equal</a> <a href='../064write-byte.subx.html#L286'>_test-output-stream</a> <span class="Constant">&quot;3b/compare *(ebp+0x00000008) 0x00000000/r32&quot;</span> <span class="Constant">&quot;F - test-compare-reg-with-mem&quot;</span>)
+<span id="L7516" class="LineNr">7516 </span>    (<a href='../058stream-equal.subx.html#L565'>check-next-stream-line-equal</a> <a href='../064write-byte.subx.html#L286'>_test-output-stream</a> <span class="Constant">&quot;3b/compare&lt;- *(ebp+0x00000008) 0x00000000/r32&quot;</span> <span class="Constant">&quot;F - test-compare-reg-with-mem&quot;</span>)
 <span id="L7517" class="LineNr">7517 </span>    <span class="subxS1Comment"># . epilogue</span>
 <span id="L7518" class="LineNr">7518 </span>    89/&lt;- %esp 5/r32/ebp
 <span id="L7519" class="LineNr">7519 </span>    5d/pop-to-ebp
diff --git a/mu_instructions b/mu_instructions
new file mode 100644
index 00000000..0571fdf0
--- /dev/null
+++ b/mu_instructions
@@ -0,0 +1,186 @@
+## Mu's instructions and their table-driven translation
+
+Mu is a statement-oriented language. Blocks consist of flat lists of instructions.
+The chart at the bottom of this page shows all the instruction forms supported
+by Mu, one to a line. Each line shows an example of the instruction on the
+left. Past the first column, everything inside the {..} is a summary of the
+data structure the Mu compiler uses (`Primitives` in apps/mu.subx) to translate
+it.
+
+The syntax of the data structure is intended to be similar to C++'s aggregate
+initializers (https://en.cppreference.com/w/cpp/language/aggregate_initialization)
+However, there are differences:
+  - We use adjacency for string concatenation.
+  - We use [] for array literals.
+  - The objects inside [] are not fully described. They include various
+    metadata about the variable in the instruction. For our purposes, assume
+    that variables on the stack have a stack offset computed for them, and
+    register variables evaluate to their register.
+  - registers may be specified by name: /eax /ecx /edx /ebx /esi /edi
+  - registers may be specified as a wildcard: /reg
+  - integer literals are always called 'n'
+  - any other variable names that don't specify a register are assumed to be on the stack
+
+There are no checks for types yet, because Mu programs only have `int` types so far.
+
+Example 1 (use the widest screen you can for this page):
+   -- instruction form --   |         -------------------------- data structure ----------------------------
+                            |<------------- pattern matching ---------->|<--- code generation ------------------->
+  var/reg <- add var2/reg   {.name="add", .inouts=[reg], .outputs=[reg], .subx-name="01/add<-", .rm32=outputs[0], .r32=inouts[0]}
+
+Read this as:
+  if an instruction's name is "add"
+    and it has one inout that's in a register
+    and it has one output that's in a register,
+  then emit the following on a single line
+    "01/add<-" (the opcode or subx-name)
+    "%{reg}", interpolating the output's register
+    "{reg}/r32", interpolating the inout's register code.
+
+Example 2:
+   -- instruction form --   |         -------------------------- data structure ----------------------------
+                            |<------- pattern matching ------>|<--- code generation ------------------->
+  add-to var, n             {.name="add-to", .inouts=[var, n], .subx-name="81 0/subop/add", .rm32="*(ebp+" inouts[0].stack-offset ")", .imm32=inouts[1]}
+
+Read this as:
+  if an instruction's name is "add-to"
+    and it has two inouts
+    the first on the stack
+    and the second a literal,
+  then emit the following on a single line
+    "81 0/subop/add" (the opcode or subx-name)
+    "*(ebp+{stack})", interpolating the first inout's stack offset
+    "{n}/imm32", interpolating the second inout's contents
+
+Ok, here's the complete chart.
+
+ -- instruction form --     |         -------------------------- data structure ----------------------------
+                            |<------------------- pattern matching ------------------->|<---- code generation ------------------->
+var/eax <- increment        {.name="increment",                       .outputs=[eax],   .subx-name="40/increment-eax"}
+var/ecx <- increment        {.name="increment",                       .outputs=[ecx],   .subx-name="41/increment-ecx"}
+var/edx <- increment        {.name="increment",                       .outputs=[edx],   .subx-name="42/increment-edx"}
+var/ebx <- increment        {.name="increment",                       .outputs=[ebx],   .subx-name="43/increment-ebx"}
+var/esi <- increment        {.name="increment",                       .outputs=[esi],   .subx-name="46/increment-esi"}
+var/edi <- increment        {.name="increment",                       .outputs=[edi],   .subx-name="47/increment-edi"}
+increment var               {.name="increment",       .inouts=[var],                    .subx-name="ff 0/subop/increment",  .rm32="*(ebp+" inouts[0].stack-offset ")"}
+
+var/eax <- decrement        {.name="decrement",                       .outputs=[eax],   .subx-name="48/decrement-eax"}
+var/ecx <- decrement        {.name="decrement",                       .outputs=[ecx],   .subx-name="49/decrement-ecx"}
+var/edx <- decrement        {.name="decrement",                       .outputs=[edx],   .subx-name="4a/decrement-edx"}
+var/ebx <- decrement        {.name="decrement",                       .outputs=[ebx],   .subx-name="4b/decrement-ebx"}
+var/esi <- decrement        {.name="decrement",                       .outputs=[esi],   .subx-name="4e/decrement-esi"}
+var/edi <- decrement        {.name="decrement",                       .outputs=[edi],   .subx-name="4f/decrement-edi"}
+decrement var               {.name="decrement",       .inouts=[var],                    .subx-name="ff 1/subop/decrement",  .rm32="*(ebp+" inouts[0].stack-offset ")"}
+
+var1/reg1 <- add var2/reg2  {.name="add",             .inouts=[reg2], .outputs=[reg1],  .subx-name="01/add<-",              .rm32=outputs[0],                           .r32=inouts[0]}
+var/reg <- add var2         {.name="add",             .inouts=[var2], .outputs=[reg],   .subx-name="03/add->",              .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=outputs[0]}
+add-to var1, var2/reg       {.name="add-to",          .inouts=[var1, var2],             .subx-name="01/add<-",              .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=inouts[1]}
+var/eax <- add n            {.name="add",             .inouts=[n],    .outputs=[eax],   .subx-name="05/add-to-eax",                                                                         .imm32=inouts[0]}
+var/reg <- add n            {.name="add",             .inouts=[n],    .outputs=[reg],   .subx-name="81 0/subop/add",        .rm32=outputs[0],                                               .imm32=inouts[0]}
+add-to var, n               {.name="add-to",          .inouts=[var, n],                 .subx-name="81 0/subop/add",        .rm32="*(ebp+" inouts[0].stack-offset ")",                      .imm32=inouts[1]}
+
+var1/reg1 <- sub var2/reg2  {.name="sub",             .inouts=[reg2], .outputs=[reg1],  .subx-name="29/sub<-",              .rm32=outputs[0],                           .r32=inouts[0]}
+var/reg <- sub var2         {.name="sub",             .inouts=[var2], .outputs=[reg],   .subx-name="2b/sub->",              .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=outputs[0]}
+sub-from var1, var2/reg     {.name="sub-from",        .inouts=[var1, var2],             .subx-name="29/sub<-",              .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=inouts[1]}
+var/eax <- sub n            {.name="sub",             .inouts=[n],    .outputs=[eax],   .subx-name="2d/sub-from-eax",                                                                       .imm32=inouts[0]}
+var/reg <- sub n            {.name="sub",             .inouts=[n],    .outputs=[reg],   .subx-name="81 5/subop/subtract",   .rm32=outputs[0],                                               .imm32=inouts[0]}
+sub-from var, n             {.name="sub-from",        .inouts=[var, n],                 .subx-name="81 5/subop/subtract",   .rm32="*(ebp+" inouts[0].stack-offset ")",                      .imm32=inouts[1]}
+
+var1/reg1 <- and var2/reg2  {.name="and",             .inouts=[reg2], .outputs=[reg1],  .subx-name="21/and<-",              .rm32=outputs[0],                           .r32=inouts[0]}
+var/reg <- and var2         {.name="and",             .inouts=[var2], .outputs=[reg],   .subx-name="23/and->",              .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=outputs[0]}
+and-with var1, var2/reg     {.name="and-with",        .inouts=[var1, reg],              .subx-name="21/and<-",              .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=inouts[1]}
+var/eax <- and n            {.name="and",             .inouts=[n],    .outputs=[eax],   .subx-name="25/and-with-eax",                                                                       .imm32=inouts[0]}
+var/reg <- and n            {.name="and",             .inouts=[n],    .outputs=[reg],   .subx-name="81 4/subop/and",        .rm32=outputs[0],                                               .imm32=inouts[0]}
+and-with var, n             {.name="and-with",        .inouts=[var, n],                 .subx-name="81 4/subop/and",        .rm32="*(ebp+" inouts[0].stack-offset ")",                      .imm32=inouts[1]}
+
+var1/reg1 <- or var2/reg2   {.name="or",              .inouts=[reg2], .outputs=[reg1],  .subx-name="09/or<-",               .rm32=outputs[0],                           .r32=inouts[0]}
+var/reg <- or var2          {.name="or",              .inouts=[var2], .outputs=[reg],   .subx-name="0b/or->",               .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=outputs[0]}
+or-with var1, var2/reg      {.name="or-with",         .inouts=[var1, reg],              .subx-name="09/or<-",               .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=inouts[1]}
+var/eax <- or n             {.name="or",              .inouts=[n],    .outputs=[eax],   .subx-name="0d/or-with-eax",                                                                        .imm32=inouts[0]}
+var/reg <- or n             {.name="or",              .inouts=[n],    .outputs=[reg],   .subx-name="81 1/subop/or",         .rm32=outputs[0],                                               .imm32=inouts[0]}
+or-with var, n              {.name="or-with",         .inouts=[var, n],                 .subx-name="81 1/subop/or",         .rm32="*(ebp+" inouts[0].stack-offset ")",                      .imm32=inouts[1]}
+
+var1/reg1 <- xor var2/reg2  {.name="xor",             .inouts=[reg2], .outputs=[reg1],  .subx-name="31/xor<-",              .rm32=outputs[0],                           .r32=inouts[0]}
+var/reg <- xor var2         {.name="xor",             .inouts=[var2], .outputs=[reg],   .subx-name="33/xor->",              .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=outputs[0]}
+xor-with var1, var2/reg     {.name="xor-with",        .inouts=[var1, reg],              .subx-name="31/xor<-",              .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=inouts[1]}
+var/eax <- xor n            {.name="xor",             .inouts=[n],    .outputs=[eax],   .subx-name="35/xor-with-eax",                                                                       .imm32=inouts[0]}
+var/reg <- xor n            {.name="xor",             .inouts=[n],    .outputs=[reg],   .subx-name="81 6/subop/xor",        .rm32=outputs[0],                                               .imm32=inouts[0]}
+xor-with var, n             {.name="xor-with",        .inouts=[var, n],                 .subx-name="81 6/subop/xor",        .rm32="*(ebp+" inouts[0].stack-offset ")",                      .imm32=inouts[1]}
+
+var/eax <- copy n           {.name="copy",            .inouts=[n],    .outputs=[eax],   .subx-name="b8/copy-to-eax",                                                                        .imm32=inouts[0]}
+var/ecx <- copy n           {.name="copy",            .inouts=[n],    .outputs=[ecx],   .subx-name="b9/copy-to-ecx",                                                                        .imm32=inouts[0]}
+var/edx <- copy n           {.name="copy",            .inouts=[n],    .outputs=[edx],   .subx-name="ba/copy-to-edx",                                                                        .imm32=inouts[0]}
+var/ebx <- copy n           {.name="copy",            .inouts=[n],    .outputs=[ebx],   .subx-name="bb/copy-to-ebx",                                                                        .imm32=inouts[0]}
+var/esi <- copy n           {.name="copy",            .inouts=[n],    .outputs=[esi],   .subx-name="be/copy-to-esi",                                                                        .imm32=inouts[0]}
+var/edi <- copy n           {.name="copy",            .inouts=[n],    .outputs=[edi],   .subx-name="bf/copy-to-edi",                                                                        .imm32=inouts[0]}
+var1/reg1 <- copy var2/reg2 {.name="copy",            .inouts=[reg2], .outputs=[reg1],  .subx-name="89/copy-to",            .rm32=outputs[0],                           .r32=inouts[0]}
+copy-to var1, var2/reg      {.name="copy-to",         .inouts=[var1, var2],             .subx-name="01/add<-",              .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=inouts[1]}
+var/reg <- copy var2        {.name="copy",            .inouts=[var2], .outputs=[reg],   .subx-name="8b/copy-from",          .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=outputs[0]}
+var/reg <- copy n           {.name="copy",            .inouts=[n],    .outputs=[reg],   .subx-name="c7 0/subop/copy",       .rm32=outputs[0],                                               .imm32=inouts[0]}
+copy-to var, n              {.name="copy-to",         .inouts=[var, n],                 .subx-name="c7 0/subop/copy",       .rm32="*(ebp+" inouts[0].stack-offset ")",                      .imm32=inouts[1]}
+
+compare var1, var2/reg      {.name="compare",         .inouts=[var1, reg],              .subx-name="39/compare->",          .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=inouts[1]}
+compare var1/reg, var2      {.name="compare",         .inouts=[reg, var2],              .subx-name="3b/compare<-",          .rm32="*(ebp+" inouts[1].stack-offset ")",  .r32=inouts[0]}
+compare var/eax, n          {.name="compare",         .inouts=[eax, n],                 .subx-name="3d/compare-eax-with",                                                                   .imm32=inouts[1]}
+compare var, n              {.name="compare",         .inouts=[var, n],                 .subx-name="81 7/subop/compare",    .rm32="*(ebp+" inouts[0].stack-offset ")",                      .imm32=inouts[1]}
+
+var/reg <- multiply var2    {.name="multiply",        .inouts=[var2], .outputs=[reg],   .subx-name="0f af/multiply",        .rm32="*(ebp+" inouts[0].stack-offset ")",  .r32=outputs[0]}
+
+Jumps have a slightly simpler format. Most of the time they take no inouts or
+outputs. Occasionally you give them a label for a block to jump to the start
+or end of.
+
+break-if-=                  {.name="break-if-=",                                        .subx-name="0f 84/jump-if-= break/disp32"}
+break-if-= label            {.name="break-if-=",      .inouts=[label],                  .subx-name="0f 84/jump-if-=",                     .disp32=inouts[0] ":break"}
+break-if-!=                 {.name="break-if-!=",                                       .subx-name="0f 85/jump-if-!= break/disp32"}
+break-if-!= label           {.name="break-if-!=",     .inouts=[label],                  .subx-name="0f 85/jump-if-!=",                    .disp32=inouts[0] ":break"}
+
+Inequalities are similar, but have unsigned and signed variants. We assume
+unsigned variants are only ever used to compare addresses.
+
+break-if-addr<              {.name="break-if-addr<",                                    .subx-name="0f 82/jump-if-addr< break/disp32"}
+break-if-addr< label        {.name="break-if-addr<",  .inouts=[label],                  .subx-name="0f 82/jump-if-addr<",                 .disp32=inouts[0] ":break"}
+break-if-addr>              {.name="break-if-addr>",                                    .subx-name="0f 87/jump-if-addr> break/disp32"}
+break-if-addr> label        {.name="break-if-addr>",  .inouts=[label],                  .subx-name="0f 87/jump-if-addr>",                 .disp32=inouts[0] ":break"}
+break-if-addr<=             {.name="break-if-addr<=",                                   .subx-name="0f 86/jump-if-addr<= break/disp32"}
+break-if-addr<= label       {.name="break-if-addr<=", .inouts=[label],                  .subx-name="0f 86/jump-if-addr<=",                .disp32=inouts[0] ":break"}
+break-if-addr>=             {.name="break-if-addr>=",                                   .subx-name="0f 83/jump-if-addr>= break/disp32"}
+break-if-addr>= label       {.name="break-if-addr>=", .inouts=[label],                  .subx-name="0f 83/jump-if-addr>=",                .disp32=inouts[0] ":break"}
+
+break-if-<                  {.name="break-if-<",                                        .subx-name="0f 8c/jump-if-< break/disp32"}
+break-if-< label            {.name="break-if-<",      .inouts=[label],                  .subx-name="0f 8c/jump-if-<",                     .disp32=inouts[0] ":break"}
+break-if->                  {.name="break-if->",                                        .subx-name="0f 8f/jump-if-> break/disp32"}
+break-if-> label            {.name="break-if->",      .inouts=[label],                  .subx-name="0f 8f/jump-if->",                     .disp32=inouts[0] ":break"}
+break-if-<=                 {.name="break-if-<=",                                       .subx-name="0f 8e/jump-if-<= break/disp32"}
+break-if-<= label           {.name="break-if-<=",     .inouts=[label],                  .subx-name="0f 8e/jump-if-<=",                    .disp32=inouts[0] ":break"}
+break-if->=                 {.name="break-if->=",                                       .subx-name="0f 8d/jump-if->= break/disp32"}
+break-if->= label           {.name="break-if->=",     .inouts=[label],                  .subx-name="0f 8d/jump-if->=",                    .disp32=inouts[0] ":break"}
+
+Finally, we repeat all the 'break' variants almost identically for 'loop'
+instructions. This works because the compiler inserts ':loop' labels at the
+start of such named blocks, and ':break' labels at the end.
+
+loop-if-=                   {.name="loop-if-=",                                         .subx-name="0f 84/jump-if-= loop/disp32"}
+loop-if-= label             {.name="loop-if-=",       .inouts=[label],                  .subx-name="0f 84/jump-if-=",                     .disp32=inouts[0] ":loop"}
+loop-if-!=                  {.name="loop-if-!=",                                        .subx-name="0f 85/jump-if-!= loop/disp32"}
+loop-if-!= label            {.name="loop-if-!=",      .inouts=[label],                  .subx-name="0f 85/jump-if-!=",                    .disp32=inouts[0] ":loop"}
+
+loop-if-addr<               {.name="loop-if-addr<",                                     .subx-name="0f 82/jump-if-addr< loop/disp32"}
+loop-if-addr< label         {.name="loop-if-addr<",   .inouts=[label],                  .subx-name="0f 82/jump-if-addr<",                 .disp32=inouts[0] ":loop"}
+loop-if-addr>               {.name="loop-if-addr>",                                     .subx-name="0f 87/jump-if-addr> loop/disp32"}
+loop-if-addr> label         {.name="loop-if-addr>",   .inouts=[label],                  .subx-name="0f 87/jump-if-addr>",                 .disp32=inouts[0] ":loop"}
+loop-if-addr<=              {.name="loop-if-addr<=",                                    .subx-name="0f 86/jump-if-addr<= loop/disp32"}
+loop-if-addr<= label        {.name="loop-if-addr<=",  .inouts=[label],                  .subx-name="0f 86/jump-if-addr<=",                .disp32=inouts[0] ":loop"}
+loop-if-addr>=              {.name="loop-if-addr>=",                                    .subx-name="0f 83/jump-if-addr>= loop/disp32"}
+loop-if-addr>= label        {.name="loop-if-addr>=",  .inouts=[label],                  .subx-name="0f 83/jump-if-addr>=",                .disp32=inouts[0] ":loop"}
+
+loop-if-<                   {.name="loop-if-<",                                         .subx-name="0f 8c/jump-if-< loop/disp32"}
+loop-if-< label             {.name="loop-if-<",       .inouts=[label],                  .subx-name="0f 8c/jump-if-<",                     .disp32=inouts[0] ":loop"}
+loop-if->                   {.name="loop-if->",                                         .subx-name="0f 8f/jump-if-> loop/disp32"}
+loop-if-> label             {.name="loop-if->",       .inouts=[label],                  .subx-name="0f 8f/jump-if->",                     .disp32=inouts[0] ":loop"}
+loop-if-<=                  {.name="loop-if-<=",                                        .subx-name="0f 8e/jump-if-<= loop/disp32"}
+loop-if-<= label            {.name="loop-if-<=",      .inouts=[label],                  .subx-name="0f 8e/jump-if-<=",                    .disp32=inouts[0] ":loop"}
+loop-if->=                  {.name="loop-if->=",                                        .subx-name="0f 8d/jump-if->= loop/disp32"}
+loop-if->= label            {.name="loop-if->=",      .inouts=[label],                  .subx-name="0f 8d/jump-if->=",                    .disp32=inouts[0] ":loop"}
+
+vim:ft=c:nowrap
diff --git a/opcodes b/subx_opcodes
index e222df66..e222df66 100644
--- a/opcodes
+++ b/subx_opcodes
diff --git a/vimrc.vim b/vimrc.vim
index c01ab5c5..88952c4b 100644
--- a/vimrc.vim
+++ b/vimrc.vim
@@ -56,9 +56,9 @@ endfunction
 command! -nargs=1 G call GrepSubX(<q-args>)
 
 if exists("&splitvertical")
-  command! -nargs=0 P hor split opcodes
+  command! -nargs=0 P hor split subx_opcodes
 else
-  command! -nargs=0 P split opcodes
+  command! -nargs=0 P split subx_opcodes
 endif
 
 " useful for inspecting just the control flow in a trace