about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2020-02-27 16:43:00 -0800
committerKartik Agaram <vc@akkartik.com>2020-02-27 16:43:00 -0800
commit127a3dae73004f1cd6328832f228436d009e4a09 (patch)
treeb48c3797591331ecaf8db0abb2d63a66050ce88c
parent5f3c324783dd111193a5ae557d9df6abee4f281b (diff)
downloadmu-127a3dae73004f1cd6328832f228436d009e4a09.tar.gz
6055 - record types and the 'get' instruction
This is a lot of code for a single test, and it took a long time to get
my data model just right. But the test coverage seems ok because it feels
mostly like straight-line code. We'll see.

I've also had to add a lot of prints. We really need app-level trace generation
pretty urgently. That requires deciding how to turn it on/off from the
commandline. And I've been reluctant to start relying on the hairy interface
that is POSIX open().
-rwxr-xr-xapps/mubin154450 -> 158713 bytes
-rw-r--r--apps/mu.subx493
2 files changed, 487 insertions, 6 deletions
diff --git a/apps/mu b/apps/mu
index 05dbe434..6444ea1b 100755
--- a/apps/mu
+++ b/apps/mu
Binary files differdiff --git a/apps/mu.subx b/apps/mu.subx
index ca4dc4b0..11114777 100644
--- a/apps/mu.subx
+++ b/apps/mu.subx
@@ -378,7 +378,7 @@ Max-type-id:
   0x10000/imm32
 
 Type-id:  # (stream (address array byte))
-  0x18/imm32/write
+  0x1c/imm32/write
   0/imm32/read
   0x100/imm32/length
   # data
@@ -388,7 +388,7 @@ Type-id:  # (stream (address array byte))
   "array"/imm32  # 3
   "handle"/imm32  # 4
   "boolean"/imm32  # 5
-  0/imm32
+  "constant"/imm32  # 6: like a literal, but replaced with its value in Var-offset
   0/imm32
   # 0x20
   0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
@@ -399,10 +399,14 @@ Type-id:  # (stream (address array byte))
   0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
   0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
 
+# Program->types contains some typeinfo for each type definition.
 # Types contain vars with types, but can't specify registers.
 Typeinfo-id:  # type-id
   0/imm32
-Typeinfo-fields:  # (handle list var)
+# each field of a type is represented using two var's:
+#   1. the input var: expected type of the field; convenient for creating using parse-var-with-type
+#   2. the output var: a constant containing the byte offset; convenient for code-generation
+Typeinfo-fields:  # (handle table string {(handle var), (handle var)})
   4/imm32
 Typeinfo-next:  # (handle typeinfo)
   8/imm32
@@ -1963,6 +1967,62 @@ test-convert-index-into-array:
     5d/pop-to-ebp
     c3/return
 
+test-convert-type-definition:
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # setup
+    (clear-stream _test-input-stream)
+    (clear-stream $_test-input-buffered-file->buffer)
+    (clear-stream _test-output-stream)
+    (clear-stream $_test-output-buffered-file->buffer)
+    c7 0/subop/copy *Next-block-index 1/imm32
+    #
+    (write _test-input-stream "fn foo a: (addr t) {\n")
+    (write _test-input-stream "  var _a/eax: (addr t) <- copy a\n")
+    (write _test-input-stream "  var b/ecx: (addr int) <- get _a, x\n")
+    (write _test-input-stream "  var c/ecx: (addr int) <- get _a, y\n")
+    (write _test-input-stream "}\n")
+    (write _test-input-stream "type t {\n")
+    (write _test-input-stream "  x: int\n")
+    (write _test-input-stream "  y: int\n")
+    (write _test-input-stream "}\n")
+    # convert
+    (convert-mu _test-input-buffered-file _test-output-buffered-file)
+    (flush _test-output-buffered-file)
+#?     # dump _test-output-stream {{{
+#?     (write 2 "^")
+#?     (write-stream 2 _test-output-stream)
+#?     (write 2 "$\n")
+#?     (rewind-stream _test-output-stream)
+#?     # }}}
+    # check output
+    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-user-defined-type/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-user-defined-type/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-user-defined-type/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-user-defined-type/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-user-defined-type/4")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-user-defined-type/5")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %eax"  "F - test-convert-function-with-user-defined-type/6")
+    (check-next-stream-line-equal _test-output-stream "    8b/copy-from *(ebp+0x00000008) 0x00000000/r32"  "F - test-convert-function-with-user-defined-type/7")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-convert-function-with-user-defined-type/8")
+    (check-next-stream-line-equal _test-output-stream "    8d/copy-address *(eax + 0x00000000) 0x00000001/r32"  "F - test-convert-function-with-user-defined-type/9")
+    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-convert-function-with-user-defined-type/10")
+    (check-next-stream-line-equal _test-output-stream "    8d/copy-address *(eax + 0x00000004) 0x00000001/r32"  "F - test-convert-function-with-user-defined-type/11")
+    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx" "F - test-convert-function-with-user-defined-type/12")
+    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx" "F - test-convert-function-with-user-defined-type/13")
+    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %eax" "F - test-convert-function-with-user-defined-type/14")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-user-defined-type/15")
+    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-user-defined-type/16")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-user-defined-type/17")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-user-defined-type/18")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-user-defined-type/19")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-user-defined-type/20")
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 #######################################################
 # Parsing
 #######################################################
@@ -1990,6 +2050,12 @@ parse-mu:  # in: (addr buffered-file)
     #       assert(vars->top == 0)
     #       *curr-function = new-function
     #       curr-function = &new-function->next
+    #     else if slice-equal?(word-slice, "type")
+    #       word-slice = next-mu-token(line)
+    #       type-id = pos-or-insert-slice(Type-id, word-slice)
+    #       var new-type: (handle typeinfo) = find-or-create-typeinfo(type-id)
+    #       assert(next-word(line) == "{")
+    #       populate-mu-type(in, new-type)
     #     else
     #       abort()
     #
@@ -2066,6 +2132,21 @@ $parse-mu:fn:
         8d/address-> *(eax+0x14) 7/r32/edi  # Function-next
         e9/jump $parse-mu:line-loop/disp32
       }
+      # if (slice-equal?(word-slice, "type")) parse a type (struct/record) definition
+      {
+$parse-mu:type:
+        (slice-equal? %edx "type")
+        3d/compare-eax-and 0/imm32
+        0f 84/jump-if-= break/disp32
+        (next-mu-token %ecx %edx)
+        # var type-id/eax: int
+        (pos-or-insert-slice Type-id %edx)  # => eax
+        # var new-type/eax: (handle typeinfo)
+        (find-or-create-typeinfo %eax)  # => eax
+        # TODO: ensure that 'line' has nothing else but '{'
+        (populate-mu-type *(ebp+8) %eax)  # => eax
+        e9/jump $parse-mu:line-loop/disp32
+      }
       # otherwise abort
       e9/jump $parse-mu:error1/disp32
     } # end line loop
@@ -2632,7 +2713,7 @@ parse-type:  # ad: (address allocation-descriptor), in: (addr stream byte) -> re
       3d/compare-eax-and 0/imm32/false
       75/jump-if-!= break/disp8
       # result->left = pos-or-insert-slice(Type-id, s)
-      (pos-or-insert-slice Type-id %ecx)
+      (pos-or-insert-slice Type-id %ecx)  # => eax
 #?       (write-buffered Stderr "=> {")
 #?       (print-int32-buffered Stderr %eax)
 #?       (write-buffered Stderr ", 0}\n")
@@ -2909,7 +2990,7 @@ $pos-or-insert-slice:end:
     5d/pop-to-ebp
     c3/return
 
-# return the index in an array of strings matching 's'
+# return the index in an array of strings matching 's', -1 if not found
 # index is denominated in elements, not bytes
 pos-slice:  # arr: (addr stream (handle array byte)), s: (addr slice) -> index/eax: int
     # . prologue
@@ -4054,6 +4135,7 @@ add-operation-and-inputs-to-stmt:  # stmt: (handle stmt), line: (addr stream byt
     50/push-eax
     51/push-ecx
     52/push-edx
+    53/push-ebx
     57/push-edi
     # edi = stmt
     8b/-> *(ebp+8) 7/r32/edi
@@ -4067,6 +4149,9 @@ $add-operation-and-inputs-to-stmt:read-operation:
     (next-mu-token *(ebp+0xc) %ecx)
     (slice-to-string Heap %ecx)  # => eax
     89/<- *(edi+4) 0/r32/eax  # Stmt1-operation or Regvardef-operation
+    # var is-get?/ebx: boolean = (name == "get")
+    (slice-equal? %ecx "get")  # => eax
+    89/<- %ebx 0/r32/eax
     {
 $add-operation-and-inputs-to-stmt:read-inouts:
       # name = next-mu-token(line)
@@ -4079,6 +4164,21 @@ $add-operation-and-inputs-to-stmt:read-inouts:
       (slice-equal? %ecx "<-")
       3d/compare-eax-and 0/imm32/false
       0f 85/jump-if-!= $add-operation-and-inputs-to-stmt:abort/disp32
+      # if (is-get? && second operand) lookup or create offset
+      {
+        81 7/subop/compare %ebx 0/imm32/false
+        74/jump-if-= break/disp8
+        81 7/subop/compare *(edi+8) 0/imm32  # Stmt1-inouts or Regvardef-inouts
+        74/jump-if-= break/disp8
+        (lookup-or-create-constant *(edi+8) %ecx)  # Stmt1-inouts => eax
+#?         (write-buffered Stderr "creating new output var ")
+#?         (print-int32-buffered Stderr %eax)
+#?         (write-buffered Stderr " for field called ")
+#?         (write-slice-buffered Stderr %ecx)
+#?         (write-buffered Stderr Newline)
+#?         (flush Stderr)
+        e9/jump $add-operation-and-inputs-to-stmt:save-var/disp32
+      }
       # is-deref? = false
       ba/copy-to-edx 0/imm32/false
       # if (slice-starts-with?(name, '*')) ++name->start and set is-deref?
@@ -4103,6 +4203,7 @@ $add-operation-and-inputs-to-stmt:end:
     81 0/subop/add %esp 8/imm32
     # . restore registers
     5f/pop-to-edi
+    5b/pop-to-ebx
     5a/pop-to-edx
     59/pop-to-ecx
     58/pop-to-eax
@@ -4489,7 +4590,7 @@ $new-function:end:
     5d/pop-to-ebp
     c3/return
 
-new-var:  # ad: (addr allocation-descriptor), name: (addr array byte), type: (addr tree type-id), block: int, stack-offset: int, register: (addr array byte) -> result/eax: (handle var)
+new-var:  # ad: (addr allocation-descriptor), name: (addr array byte), type: (addr tree type-id), block: int, offset: int, register: (addr array byte) -> result/eax: (handle var)
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -4771,6 +4872,334 @@ $append-to-block:end:
     5d/pop-to-ebp
     c3/return
 
+## Parsing types
+# We need to create metadata on user-defined types, and we need to use this
+# metadata as we parse instructions.
+# However, we also want to allow types to be used before their definitions.
+# This means we can't ever assume any type data structures exist.
+
+lookup-or-create-constant:  # container: (handle stmt-var), field-name: (addr slice) -> result/eax: (handle var)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    56/push-esi
+    # var container-type/esi: type-id
+    (container-type *(ebp+8))  # => eax
+#?     (write-buffered Stderr "lookup-or-create-constant: container type-id: ")
+#?     (print-int32-buffered Stderr %eax)
+#?     (write-buffered Stderr Newline)
+#?     (flush Stderr)
+    89/<- %esi 0/r32/eax
+    # var typeinfo/eax: (addr typeinfo)
+    (find-or-create-typeinfo %esi)  # => eax
+#?     (write-buffered Stderr "lookup-or-create-constant: typeinfo: ")
+#?     (print-int32-buffered Stderr %eax)
+#?     (write-buffered Stderr Newline)
+#?     (flush Stderr)
+    # result = find-or-create-typeinfo-constant(typeinfo, field-name)
+    (find-or-create-typeinfo-constant %eax *(ebp+0xc))  # => eax
+$lookup-or-create-constant:end:
+    # . restore registers
+    5e/pop-to-esi
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+# container->var->type->right->left->value
+container-type:  # container: (handle stmt-var) -> result/eax: type-id
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    #
+    8b/-> *(ebp+8) 0/r32/eax
+    8b/-> *eax 0/r32/eax  # Stmt-var-value
+    8b/-> *(eax+4) 0/r32/eax  # Var-type
+    8b/-> *(eax+4) 0/r32/eax  # Tree-right
+    8b/-> *eax 0/r32/eax  # Tree-left
+    8b/-> *eax 0/r32/eax  # Atom-value
+$container-type:end:
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+find-or-create-typeinfo:  # t: type-id -> result/eax: (handle typeinfo)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    51/push-ecx
+    # eax = find-typeinfo(t)
+    (find-typeinfo *(ebp+8))  # => eax
+    {
+      # if (curr != 0) break
+      3d/compare-eax-and 0/imm32
+      75/jump-if-!= break/disp8
+$find-or-create-typeinfo:create:
+      (allocate Heap *Typeinfo-size)  # => eax
+      (zero-out %eax *Typeinfo-size)
+      # result->id = t
+      8b/-> *(ebp+8) 1/r32/ecx
+      89/<- *eax 1/r32/ecx  # Typeinfo-id
+      # result->fields = new table
+      50/push-eax
+      (new-stream Heap 0x40 0xc)  # => eax
+      89/<- %ecx 0/r32/eax
+      58/pop-to-eax
+      89/<- *(eax+4) 1/r32/ecx
+      # result->next = Program->types
+      8b/-> *_Program-types 1/r32/ecx
+      89/<- *(eax+8) 1/r32/ecx  # Typeinfo-next
+      # Program->types = result
+      89/<- *_Program-types 0/r32/eax
+    }
+$find-or-create-typeinfo:end:
+    # . restore registers
+    59/pop-to-ecx
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+find-typeinfo:  # t: type-id -> result/eax: (handle typeinfo)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    51/push-ecx
+    # ecx = t
+    8b/-> *(ebp+8) 1/r32/ecx
+    # var curr/eax: (handle typeinfo) = Program->types
+    8b/-> *_Program-types 0/r32/eax
+    {
+      # if (curr == 0) break
+      3d/compare-eax-and 0/imm32
+      74/jump-if-= break/disp8
+      # if (curr->id == t) return curr
+      39/compare *eax 1/r32/ecx  # Typeinfo-id
+      0f 84/jump-if-= $find-or-create-typeinfo:end/disp32
+      # curr = curr->next
+      8b/-> *(eax+8) 0/r32/eax
+      #
+      eb/jump loop/disp8
+    }
+$find-typeinfo:end:
+    # . restore registers
+    59/pop-to-ecx
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+find-or-create-typeinfo-constant:  # T: (handle typeinfo), f: (addr slice) -> result/eax: (handle var)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    51/push-ecx
+    56/push-esi
+    # esi = T->fields
+    8b/-> *(ebp+8) 6/r32/esi
+    8b/-> *(esi+4) 6/r32/esi  # Typeinfo-fields
+    # esi = get-or-insert(T->fields, f)
+    (leaky-get-or-insert-slice %esi *(ebp+0xc) 0xc)  # => eax
+    89/<- %esi 0/r32/eax
+    # if output var exists, return it
+    81 7/subop/compare *(esi+4) 0/imm32  # output var
+    8b/-> *(esi+4) 0/r32/eax  # output var
+    75/jump-if-!= $find-or-create-typeinfo-constant:end/disp8
+    # var type/ecx: (handle tree type-id) = new var("dummy name", constant type, -1 offset)
+    (allocate Heap *Tree-size)  # => eax
+    c7 0/subop/copy *eax 6/imm32/constant  # Atom-value
+    c7 0/subop/copy *(eax+4) 0/imm32  # Tree-right
+    (new-var Heap "field" %eax 0 -1 0)  # => eax
+    # offset (constant value) isn't filled out yet
+    # save output var in row
+    89/<- *(esi+4) 0/r32/eax
+$find-or-create-typeinfo-constant:end:
+    # . restore registers
+    5e/pop-to-esi
+    59/pop-to-ecx
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+populate-mu-type:  # in: (addr stream byte), t: (handle typeinfo)
+    # pseudocode:
+    #   var line: (stream byte 512)
+    #   curr-offset = 0
+    #   while true
+    #     clear-stream(line)
+    #     read-line-buffered(in, line)
+    #     if line->write == 0
+    #       abort
+    #     word-slice = next-mu-token(line)
+    #     if slice-empty?(word-slice)               # end of line
+    #       continue
+    #     if slice-equal?(word-slice, "}")
+    #       break
+    #     var v: (handle var) = parse-var-with-type(word-slice, line)
+    #     var r: (addr {(handle var) (handle var)}) = get-or-insert(t, v->name, row-size=12)
+    #     TODO: ensure that r->first is null
+    #     r->first = v
+    #     if r->second == 0
+    #       r->second = new var("dummy name", constant type, -1 offset)
+    #     r->second->offset = curr-offset
+    #     curr-offset += size-of(existing)
+    #     TODO: ensure nothing else in line
+    #
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    52/push-edx
+    53/push-ebx
+    56/push-esi
+    57/push-edi
+    # edi = t
+    8b/-> *(ebp+0xc) 7/r32/edi
+    # var line/ecx: (stream byte 512)
+    81 5/subop/subtract %esp 0x200/imm32
+    68/push 0x200/imm32/length
+    68/push 0/imm32/read
+    68/push 0/imm32/write
+    89/<- %ecx 4/r32/esp
+    # var word-slice/edx: slice
+    68/push 0/imm32/end
+    68/push 0/imm32/start
+    89/<- %edx 4/r32/esp
+    # var curr-offset/ebx: int = 0
+    bb/copy-to-ebx 0/imm32
+    {
+$populate-mu-type:line-loop:
+      (clear-stream %ecx)
+      (read-line-buffered *(ebp+8) %ecx)
+      # if (line->write == 0) abort
+      81 7/subop/compare *ecx 0/imm32
+      0f 84/jump-if-= $populate-mu-type:abort/disp32
+#?       # dump line {{{
+#?       (write 2 "parse-mu: ^")
+#?       (write-stream 2 %ecx)
+#?       (write 2 "$\n")
+#?       (rewind-stream %ecx)
+#?       # }}}
+      (next-mu-token %ecx %edx)
+      # if slice-empty?(word-slice) continue
+      (slice-empty? %edx)
+      3d/compare-eax-and 0/imm32
+      0f 85/jump-if-!= loop/disp32
+      # if slice-equal?(word-slice, "}") break
+      (slice-equal? %edx "}")
+      3d/compare-eax-and 0/imm32
+      0f 85/jump-if-!= break/disp32
+      # var v/esi: (handle var) = parse-var-with-type(word-slice, first-line)
+      (parse-var-with-type %edx %ecx)  # => eax
+      89/<- %esi 0/r32/eax
+      # var r/eax: (addr {(handle var) (handle var)})
+#?       (write-buffered Stderr "populate-mu-type: typeinfo: ")
+#?       (print-int32-buffered Stderr %edi)
+#?       (write-buffered Stderr Newline)
+#?       (flush Stderr)
+      (get-or-insert *(edi+4) *esi 0xc)  # Typeinfo-fields Var-name => eax
+      # r->first = v
+      89/<- *eax 6/r32/esi
+      # if (r->second == 0) create a new var with some placeholder data
+      {
+        81 7/subop/compare *(eax+4) 0/imm32
+        75/jump-if-!= break/disp8
+        # temporarily spill r to esi
+        89/<- %esi 0/r32/eax
+        (new-literal Heap %edx)  # => eax
+        89/<- *(esi+4) 0/r32/eax
+        89/<- %eax 6/r32/esi
+#?         (write-buffered Stderr "creating new output var ")
+#?         (print-int32-buffered Stderr %eax)
+#?         (write-buffered Stderr Newline)
+#?         (flush Stderr)
+      }
+      # r->second->offset = curr-offset
+      8b/-> *(eax+4) 0/r32/eax
+#?       (write-buffered Stderr "writing offset ")
+#?       (print-int32-buffered Stderr %ebx)
+#?       (write-buffered Stderr " to output var ")
+#?       (print-int32-buffered Stderr %eax)
+#?       (write-buffered Stderr Newline)
+#?       (flush Stderr)
+      89/<- *(eax+0xc) 3/r32/ebx
+      # curr-offset += size-of(v)
+      50/push-eax
+      (size-of %eax)  # => eax
+      01/add-to %ebx 0/r32/eax
+      58/pop-to-eax
+      #
+      e9/jump loop/disp32
+    }
+$populate-mu-type:end:
+    # . reclaim locals
+    81 0/subop/add %esp 0x214/imm32
+    # . restore registers
+    5f/pop-to-edi
+    5e/pop-to-esi
+    5b/pop-to-ebx
+    5a/pop-to-edx
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+$populate-mu-type:abort:
+    # error("unexpected top-level command: " word-slice "\n")
+    (write-buffered Stderr "incomplete type definition '")
+    (type-name *edi)  # Typeinfo-id => eax
+    (write-buffered Stderr %eax)
+    (write-buffered Stderr "\n")
+    (flush Stderr)
+    # . syscall(exit, 1)
+    bb/copy-to-ebx  1/imm32
+    b8/copy-to-eax  1/imm32/exit
+    cd/syscall  0x80/imm8
+    # never gets here
+
+type-name:  # index: int -> result/eax: (addr array byte)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    #
+    (index Type-id *(ebp+8))
+$type-name:end:
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+index:  # arr: (addr stream (handle array byte)), index: int -> result/eax: (addr array byte)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    56/push-esi
+    # TODO: bounds-check index
+    # esi = arr
+    8b/-> *(ebp+8) 6/r32/esi
+    # eax = index
+    8b/-> *(ebp+0xc) 0/r32/eax
+    # eax = *(arr + 12 + index)
+    8b/-> *(esi+eax+0xc) 0/r32/eax
+$index:end:
+    # . restore registers
+    5e/pop-to-esi
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 #######################################################
 # Type-checking
 #######################################################
@@ -5626,6 +6055,32 @@ $emit-subx-stmt-list:index:
       e9/jump $emit-subx-stmt:end/disp32
     }
     # }}}
+    # get field from record {{{
+    {
+      # if (!string-equal?(var->operation, "get")) break
+      (string-equal? *(ecx+4) "get")  # Stmt1-operation => eax
+      3d/compare-eax-and 0/imm32
+      0f 84/jump-if-= break/disp32
+$emit-subx-stmt-list:get:
+      (emit-indent *(ebp+8) *Curr-block-depth)
+      (write-buffered *(ebp+8) "8d/copy-address *(")
+      # inouts[0]->register " + "
+      8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts
+      8b/-> *eax 0/r32/eax  # Stmt-var-value
+      (write-buffered *(ebp+8) *(eax+0x10))  # Var-register => eax
+      #
+      (write-buffered *(ebp+8) " + ")
+      (print-mu-get-offset *(ebp+8) %ecx)
+      (write-buffered *(ebp+8) ") ")
+      # outputs[0] "/r32"
+      8b/-> *(ecx+0xc) 0/r32/eax  # Stmt1-outputs
+      8b/-> *eax 0/r32/eax  # List-value
+      (get Registers *(eax+0x10) 8 "Registers")  # Var-register => eax
+      (print-int32-buffered *(ebp+8) *eax)
+      (write-buffered *(ebp+8) "/r32\n")
+      e9/jump $emit-subx-stmt:end/disp32
+    }
+    # }}}
     # if stmt matches a primitive, emit it
     {
 $emit-subx-stmt:check-for-primitive:
@@ -5670,6 +6125,32 @@ $emit-subx-stmt:abort:
     cd/syscall  0x80/imm8
     # never gets here
 
+print-mu-get-offset:  # out: (addr buffered-file), stmt: (handle stmt)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    # var second-inout/eax: (handle stmt-var) = stmt->inouts->next
+    8b/-> *(ebp+0xc) 0/r32/eax
+    8b/-> *(eax+8) 0/r32/eax  # Stmt1-inouts
+    8b/-> *(eax+4) 0/r32/eax  # Stmt-var-next
+    # var output-var/eax: (handle var) = second-inout->value
+    8b/-> *eax 0/r32/eax  # Stmt-var-value
+    # print offset
+#?     (write-buffered Stderr "emitting offset from output var ")
+#?     (print-int32-buffered Stderr %eax)
+#?     (write-buffered Stderr Newline)
+#?     (flush Stderr)
+    (print-int32-buffered *(ebp+8) *(eax+0xc))  # Var-offset
+$emit-get-offset:end:
+    # . restore registers
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 emit-subx-block:  # out: (addr buffered-file), block: (handle block), vars: (addr stack (handle var))
     # . prologue
     55/push-ebp