about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--400.mu159
-rw-r--r--400.txt1
-rwxr-xr-xapps/mubin341189 -> 344970 bytes
-rw-r--r--apps/mu.subx350
-rw-r--r--mu.vim2
5 files changed, 500 insertions, 12 deletions
diff --git a/400.mu b/400.mu
new file mode 100644
index 00000000..04e93b67
--- /dev/null
+++ b/400.mu
@@ -0,0 +1,159 @@
+# The 4xx series is for primitives implemented in Mu.
+
+# Signatures for major SubX functions defined so far.
+
+# autogenerated
+sig run-tests
+
+# init.linux
+# TODO: make this OS-specific
+# TODO: include result type at least, even if register args are too much
+sig syscall_exit  # status/ebx: int
+sig syscall_read  # fd/ebx: int, buf/ecx: addr, size/edx: int -> nbytes-or-error/eax: int
+sig syscall_write  # fd/ebx: int, buf/ecx: addr, size/edx: int -> nbytes-or-error/eax: int
+sig syscall_open  # filename/ebx: (addr kernel-string), flags/ecx: int, dummy=0x180/edx -> fd-or-error/eax: int
+sig syscall_close  # fd/ebx: int -> status/eax
+sig syscall_creat  # filename/ebx: (addr kernel-string) -> fd-or-error/eax: int
+sig syscall_unlink  # filename/ebx: (addr kernel-string) -> status/eax: int
+sig syscall_rename  # source/ebx: (addr kernel-string), dest/ecx: (addr kernel-string) -> status/eax: int
+sig syscall_mmap  # arg/ebx: (addr mmap_arg_struct) -> status/eax: int
+sig syscall_ioctl  # fd/ebx: int, cmd/ecx: int, arg/edx: (addr _)
+sig syscall_nanosleep  # req/ebx: (addr timespec)
+sig syscall_clock_gettime  # clock/ebx: int, out/ecx: (addr timespec)
+
+# Generated using:
+#   grep -h '^[a-z]' [0-9]*.subx |grep -v '^test-'
+# Functions we don't want to make accessible from Mu are commented out.
+# Many functions here may not be usable yet because of missing features
+# (global variable support, type definitions for stuff like `stream`)
+sig check-ints-equal a: int, b: int, msg: (addr array byte)
+sig kernel-string-equal? s: (addr kernel-string), benchmark: (addr array byte) -> result/eax: boolean
+sig new-segment len: int, ad: (addr allocation-descriptor)
+sig string-equal? s: (addr array byte), benchmark: (addr array byte) -> result/eax: boolean
+sig string-starts-with? s: (addr array byte), benchmark: (addr array byte) -> result/eax: boolean
+sig check-strings-equal s: (addr array byte), expected: (addr array byte), msg: (addr array byte)
+sig clear-stream f: (addr stream byte)
+sig rewind-stream f: (addr stream byte)
+sig initialize-trace-stream n: int
+sig trace line: (addr array byte)
+sig check-trace-contains line: (addr string), msg: (addr string)
+sig check-trace-scans-to line: (addr string), msg: (addr string)
+sig trace-scan line: (addr array byte) -> result/eax: boolean
+sig next-line-matches? t: (addr stream byte), line: (addr array byte) -> result/eax: boolean
+sig skip-next-line t: (addr stream byte)
+sig clear-trace-stream
+#sig write f: fd or (addr stream byte), s: (addr array byte)
+sig stream-data-equal? f: (addr stream byte), s: (addr array byte) -> result/eax: boolean
+sig check-stream-equal f: (addr stream byte), s: (addr array byte), msg: (addr array byte)
+sig next-stream-line-equal? f: (addr stream byte), s: (addr array byte) -> result/eax: boolean
+sig check-next-stream-line-equal
+sig tailor-exit-descriptor ed: (addr exit-descriptor), nbytes: int
+sig stop ed: (addr exit-descriptor), value: int
+#sig read f: fd or (addr stream byte), s: (addr stream byte) -> num-bytes-read/eax: int
+sig read-byte-buffered f: (addr buffered-file) -> byte-or-Eof/eax: int
+#sig write-stream f: fd or (addr stream byte), s: (addr stream byte)
+#sig error ed: (addr exit-descriptor), out: fd or (addr stream byte), msg: (addr array byte)
+sig write-byte-buffered f: (addr buffered-file), n: int
+sig flush f: (addr buffered-file)
+sig append-byte f: (addr stream byte), n: int
+sig write-buffered f: (addr buffered-file), msg: (addr array byte)
+#sig to-hex-char in/eax: int -> out/eax: int
+sig append-byte-hex f: (addr stream byte), n: int
+sig write-byte-hex-buffered f: (addr buffered-file), n: int
+sig write-int32-hex f: (addr stream byte), n: int
+sig write-int32-hex-buffered f: (addr buffered-file), n: int
+sig is-hex-int? in: (addr slice) -> result/eax: boolean
+sig parse-hex-int in: (addr array byte) -> result/eax: int
+sig parse-hex-int-from-slice in: (addr slice) -> result/eax: int
+#sig parse-hex-int-helper start: (addr byte), end: (addr byte) -> result/eax: int
+sig is-hex-digit? c: byte -> result/eax: boolean
+#sig from-hex-char in/eax: byte -> out/eax: nibble
+sig error-byte ed: (addr exit-descriptor), out: (addr buffered-file), msg: (addr array byte), n: byte
+#sig allocate ad: (addr allocation-descriptor), n: int, out: (addr handle)
+#sig allocate-raw ad: (addr allocation-descriptor), n: int, out: (addr handle)
+sig lookup h: (handle T) -> result/eax: (addr T)
+sig handle-equal? a: handle, b: handle -> result/eax: boolean
+sig copy-handle src: handle, dest: (addr handle)
+sig allocate-region ad: (addr allocation-descriptor), n: int, out: (addr handle allocation-descriptor)
+sig allocate-array ad: (addr allocation-descriptor), n: int, out: (addr handle)
+sig copy-array ad: (addr allocation-descriptor), src: (addr array), out: (addr handle)
+sig zero-out start: (addr byte), len: int
+sig new-stream ad: (addr allocation-descriptor), length: int, elemsize: int, out: (addr handle stream _)
+sig read-line-buffered f: (addr buffered-file), s: (addr stream byte)
+sig read-line f: (addr stream byte), s: (addr stream byte)
+sig slice-empty? s: (addr slice) -> result/eax: boolean
+sig slice-equal? s: (addr slice), p: (addr array byte) -> result/eax: boolean
+sig slice-starts-with? s: (addr slice), head: (addr array byte) -> result/eax: boolean
+sig write-slice out: (addr stream byte), s: (addr slice)
+sig write-slice-buffered out: (addr buffered-file), s: (addr slice)
+sig slice-to-string ad: (addr allocation-descriptor), in: (addr slice), out: (addr handle array byte)
+sig next-token in: (addr stream byte), delimiter: byte, out: (addr slice)
+sig next-token-from-slice start: (addr byte), end: (addr byte), delimiter: byte, out: (addr slice)
+sig skip-chars-matching in: (addr stream byte), delimiter: byte
+sig skip-chars-matching-whitespace in: (addr stream byte)
+sig skip-chars-not-matching in: (addr stream byte), delimiter: byte
+sig skip-chars-not-matching-whitespace in: (addr stream byte)
+sig skip-chars-matching-in-slice curr: (addr byte), end: (addr byte), delimiter: byte -> curr/eax: (addr byte)
+sig skip-chars-matching-whitespace-in-slice curr: (addr byte), end: (addr byte) -> curr/eax: (addr byte)
+sig skip-chars-not-matching-in-slice curr: (addr byte), end: (addr byte), delimiter: byte -> curr/eax: (addr byte)
+sig skip-chars-not-matching-whitespace-in-slice curr: (addr byte), end: (addr byte) -> curr/eax: (addr byte)
+sig skip-string line: (addr stream byte)
+sig skip-string-in-slice curr: (addr byte), end: (addr byte) -> new_curr/eax: (addr byte)
+sig skip-until-close-paren line: (addr stream byte)
+sig skip-until-close-paren-in-slice curr: (addr byte), end: (addr byte) -> new_curr/eax: (addr byte)
+sig write-stream-data f: (addr buffered-file), s: (addr stream byte)
+sig write-int32-decimal out: (addr stream byte), n: int32
+sig is-decimal-digit? c: byte -> result/eax: boolean
+sig to-decimal-digit in: byte -> out/eax: int
+sig next-word line: (addr stream byte), out: (addr slice)
+sig has-metadata? word: (addr slice), s: (addr string) -> result/eax: boolean
+sig is-valid-name? in: (addr slice) -> result/eax: boolean
+sig is-label? word: (addr slice) -> result/eax: boolean
+sig emit-hex out: (addr buffered-file), n: int, width: int
+sig emit out: (addr buffered-file), word: (addr slice), width: int
+#sig get table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int, abort-message-prefix: (addr array byte) -> result/eax: (addr T)
+#sig get-slice table: (addr stream {(handle array byte), T}), key: (addr slice), row-size: int, abort-message-prefix: (addr array byte) -> result/eax: (addr T)
+#sig get-or-insert table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int, ad: (addr allocation-descriptor) -> result/eax: (addr T)
+#sig get-or-insert-handle table: (addr stream {(handle array byte), T}), key: (handle array byte), row-size: int -> result/eax: (addr T)
+#sig get-or-insert-slice table: (addr stream {(handle array byte), T}), key: (addr slice), row-size: int, ad: (addr allocation-descriptor) -> result/eax: (addr T)
+#sig get-or-stop table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int,
+#sig get-slice-or-stop table: (addr stream {(handle array byte), _}), key: (addr slice), row-size: int,
+#sig maybe-get table: (addr stream {(handle array byte), T}), key: (addr array byte), row-size: int -> result/eax: (addr T)
+#sig maybe-get-slice table: (addr stream {(handle array byte), T}), key: (addr slice), row-size: int -> result/eax: (addr T)
+sig slurp f: (addr buffered-file), s: (addr stream byte)
+sig compute-width word: (addr array byte) -> result/eax: int
+sig compute-width-of-slice s: (addr slice) -> result/eax: int
+sig emit-hex-array out: (addr buffered-file), arr: (addr array byte)
+sig next-word-or-string line: (addr stream byte), out: (addr slice)
+sig write-int out: (addr stream byte), n: int
+sig clear-stack s: (addr stack)
+sig push s: (addr stack), n: int
+sig pop s: (addr stack) -> n/eax: int
+sig top s: (addr stack) -> n/eax: int
+sig array-equal? a: (addr array int), b: (addr array int) -> result/eax: boolean
+sig parse-array-of-ints ad: (addr allocation-descriptor), s: (addr string), out: (addr handle array int)
+sig check-array-equal a: (addr array int), expected: (addr string), msg: (addr string)
+#sig push-n-zero-bytes n: int
+sig kernel-string-to-string ad: (addr allocation-descriptor), in: (addr kernel-string), out: (addr handle array byte)
+sig kernel-string-length in: (addr kernel-string) -> result/eax: int
+sig enable-screen-grid-mode
+sig enable-screen-type-mode
+sig screen-size -> nrows/eax: int, ncols/ecx: int
+sig clear-screen
+sig move-cursor-on-screen row: int, column: int
+sig print-string-to-screen s: (addr array byte)
+sig print-byte-to-screen c: byte
+sig print-int32-hex-to-screen n: int
+sig reset-formatting-on-screen
+sig start-color-on-screen fg: int, bg: int
+sig start-bold-on-screen
+sig start-underline-on-screen
+sig start-reverse-video-on-screen
+sig start-blinking-on-screen
+sig hide-cursor-on-screen
+sig show-cursor-on-screen
+sig enable-keyboard-immediate-mode
+sig enable-keyboard-type-mode
+sig read-key -> result/eax: byte
+sig open filename: (addr array byte), write?: boolean, out: (addr handle buffered-file)
+sig size in: (addr array _) -> result/eax: int
diff --git a/400.txt b/400.txt
deleted file mode 100644
index 81548ce5..00000000
--- a/400.txt
+++ /dev/null
@@ -1 +0,0 @@
-The 4xx series is for primitives implemented in Mu.
diff --git a/apps/mu b/apps/mu
index 4e8d6741..e1e82141 100755
--- a/apps/mu
+++ b/apps/mu
Binary files differdiff --git a/apps/mu.subx b/apps/mu.subx
index edd8a02a..d8c38e0a 100644
--- a/apps/mu.subx
+++ b/apps/mu.subx
@@ -243,6 +243,10 @@ _Program-types:  # (handle typeinfo)
   0/imm32
 _Program-types->payload:
   0/imm32
+_Program-signatures:  # (handle function)
+  0/imm32
+_Program-signatures->payload:
+  0/imm32
 
 # Some constants for simulating the data structures described above.
 # Many constants here come with a type in a comment.
@@ -850,7 +854,6 @@ test-convert-function-with-literal-arg-2:
     5d/pop-to-ebp
     c3/return
 
-# HERE
 test-convert-function-call-with-literal-arg:
     # . prologue
     55/push-ebp
@@ -910,6 +913,48 @@ test-convert-function-call-with-literal-arg:
     5d/pop-to-ebp
     c3/return
 
+test-convert-function-call-with-signature:
+    # . 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)
+    #
+    (write _test-input-stream "fn main -> result/ebx: int {\n")
+    (write _test-input-stream "  result <- do-add 3 4\n")
+    (write _test-input-stream "}\n")
+    (write _test-input-stream "sig do-add a: int, b: int -> result/ebx: int\n")
+    # convert
+    (convert-mu _test-input-buffered-file _test-output-buffered-file Stderr 0)
+    (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 "main:"                   "F - test-convert-function-call-with-signature/0")
+    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-call-with-signature/1")
+    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-call-with-signature/2")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-call-with-signature/3")
+    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-call-with-signature/4")
+    (check-next-stream-line-equal _test-output-stream "$main:0x00000001:loop:"  "F - test-convert-function-call-with-signature/5")
+    (check-next-stream-line-equal _test-output-stream "    (do-add 3 4)"        "F - test-convert-function-call-with-signature/6")
+    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-call-with-signature/7")
+    (check-next-stream-line-equal _test-output-stream "$main:0x00000001:break:" "F - test-convert-function-call-with-signature/8")
+    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-call-with-signature/9")
+    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-call-with-signature/10")
+    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-call-with-signature/11")
+    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-call-with-signature/12")
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 test-convert-function-with-local-var-in-mem:
     # . prologue
     55/push-ebp
@@ -5921,6 +5966,7 @@ Curr-block-depth:  # (addr int)
 parse-mu:  # in: (addr buffered-file), err: (addr buffered-file), ed: (addr exit-descriptor)
     # pseudocode
     #   var curr-function: (addr handle function) = Program->functions
+    #   var curr-signature: (addr handle function) = Program->signatures
     #   var curr-type: (addr handle typeinfo) = Program->types
     #   var line: (stream byte 512)
     #   var word-slice: slice
@@ -5941,6 +5987,11 @@ parse-mu:  # in: (addr buffered-file), err: (addr buffered-file), ed: (addr exit
     #       assert(vars->top == 0)
     #       *curr-function = new-function
     #       curr-function = &new-function->next
+    #     else if slice-equal?(word-slice, "sig")
+    #       var new-function: (handle function) = allocate(function)
+    #       populate-mu-function-signature(line, new-function)
+    #       *curr-signature = new-function
+    #       curr-signature = &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)
@@ -5953,6 +6004,8 @@ parse-mu:  # in: (addr buffered-file), err: (addr buffered-file), ed: (addr exit
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
+    # var curr-signature: (addr handle function) at *(ebp-4)
+    68/push _Program-signatures/imm32
     # . save registers
     50/push-eax
     51/push-ecx
@@ -6037,6 +6090,46 @@ $parse-mu:fn:
         #
         e9/jump $parse-mu:line-loop/disp32
       }
+      # if (slice-equal?(word-slice, "sig")) parse a function signature
+      # Function signatures are for providing types to SubX functions.
+      {
+$parse-mu:sig:
+        (slice-equal? %edx "sig")  # => eax
+        3d/compare-eax-and 0/imm32/false
+        0f 84/jump-if-= break/disp32
+        # edi = curr-function
+        57/push-edi
+$bb:
+        8b/-> *(ebp-4) 7/r32/edi
+        # var new-function/esi: (handle function)
+        68/push 0/imm32
+        68/push 0/imm32
+        89/<- %esi 4/r32/esp
+        # populate-mu-function(line, in, vars, new-function)
+        (allocate Heap *Function-size %esi)
+        # var new-function-addr/eax: (addr function)
+        (lookup *esi *(esi+4))  # => eax
+        #
+        (populate-mu-function-signature %ecx %eax *(ebp+0xc) *(ebp+0x10))
+        # *curr-signature = new-function
+        8b/-> *esi 0/r32/eax
+        89/<- *edi 0/r32/eax
+        8b/-> *(esi+4) 0/r32/eax
+        89/<- *(edi+4) 0/r32/eax
+        # curr-signature = &new-function->next
+        # . var tmp/eax: (addr function) = lookup(new-function)
+        (lookup *esi *(esi+4))  # => eax
+        # . curr-function = &tmp->next
+        8d/copy-address *(eax+0x20) 7/r32/edi  # Function-next
+        # reclaim new-function
+        81 0/subop/add %esp 8/imm32
+        # save curr-function
+        89/<- *(ebp-4) 7/r32/edi
+        # restore edi
+        5f/pop-to-edi
+        #
+        e9/jump $parse-mu:line-loop/disp32
+      }
       # if (slice-equal?(word-slice, "type")) parse a type (struct/record) definition
       {
 $parse-mu:type:
@@ -6080,6 +6173,8 @@ $parse-mu:end:
     5a/pop-to-edx
     59/pop-to-ecx
     58/pop-to-eax
+    # . reclaim local
+    81 0/subop/add %esp 4/imm32
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
@@ -6167,7 +6262,6 @@ populate-mu-function-header:  # first-line: (addr stream byte), out: (addr funct
     # read function name
     (next-mu-token *(ebp+8) %ecx)
     # error checking
-    # TODO: error if word-slice starts with 'break' or 'loop'
     # if (word-slice == '{') abort
     (slice-equal? %ecx "{")   # => eax
     3d/compare-eax-and 0/imm32/false
@@ -6274,17 +6368,28 @@ $populate-mu-function-header:error1:
     # never gets here
 
 $populate-mu-function-header:error2:
-    # error("function inout '" var "' cannot be in a register")
-    (write-buffered *(ebp+0x14) "function inout '")
-    (write-buffered *(ebp+0x14) *ebx)  # Var-name
+    # error("fn " fn ": function inout '" var "' cannot be in a register")
+    (write-buffered *(ebp+0x14) "fn ")
+    50/push-eax
+    (lookup *edi *(edi+4))  # Function-name Function-name => eax
+    (write-buffered *(ebp+0x14) %eax)
+    58/pop-to-eax
+    (write-buffered *(ebp+0x14) ": function inout '")
+    (lookup *eax *(eax+4))  # Var-name Var-name => eax
+    (write-buffered *(ebp+0x10) %eax)
     (write-buffered *(ebp+0x14) "' cannot be in a register")
     (flush *(ebp+0x14))
     (stop *(ebp+0x18) 1)
     # never gets here
 
 $populate-mu-function-header:error3:
-    # error("function output '" var "' must be in a register")
-    (write-buffered *(ebp+0x14) "function output '")
+    # error("fn " fn ": function output '" var "' must be in a register")
+    (write-buffered *(ebp+0x14) "fn ")
+    50/push-eax
+    (lookup *edi *(edi+4))  # Function-name Function-name => eax
+    (write-buffered *(ebp+0x14) %eax)
+    58/pop-to-eax
+    (write-buffered *(ebp+0x14) ": function output '")
     (lookup *ebx *(ebx+4))  # => eax
     (lookup *eax *(eax+4))  # Var-name Var-name => eax
     (write-buffered *(ebp+0x14) %eax)
@@ -6296,6 +6401,203 @@ $populate-mu-function-header:error3:
     (stop *(ebp+0x18) 1)
     # never gets here
 
+# scenarios considered:
+# ✓ fn foo
+# ✗ fn foo {
+# ✓ fn foo x
+# ✓ fn foo x: int
+# ✓ fn foo x: int -> y/eax: int
+# TODO:
+#   disallow outputs of type `(... addr ...)`
+#   disallow inputs of type `(... addr ... addr ...)`
+populate-mu-function-signature:  # first-line: (addr stream byte), out: (addr function), err: (addr buffered-file), ed: (addr exit-descriptor)
+    # pseudocode:
+    #   var word-slice: slice
+    #   next-mu-token(first-line, word-slice)
+    #   assert(word-slice not in '{' '}' '->')
+    #   out->name = slice-to-string(word-slice)
+    #   ## inouts
+    #   while true
+    #     word-slice = next-mu-token(first-line)
+    #     if slice-empty?(word-slice) break
+    #     if (word-slice == '->') break
+    #     assert(word-slice not in '{' '}')
+    #     var v: (handle var) = parse-var-with-type(word-slice, first-line)
+    #     assert(v->register == null)
+    #     # v->block-depth is implicitly 0
+    #     out->inouts = append(v, out->inouts)
+    #   ## outputs
+    #   while true
+    #     word-slice = next-mu-token(first-line)
+    #     if slice-empty?(word-slice) break
+    #     assert(word-slice not in '{' '}' '->')
+    #     var v: (handle var) = parse-var-with-type(word-slice, first-line)
+    #     assert(v->register != null)
+    #     out->outputs = append(v, out->outputs)
+    #
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    52/push-edx
+    53/push-ebx
+    57/push-edi
+    # edi = out
+    8b/-> *(ebp+0xc) 7/r32/edi
+    # var word-slice/ecx: slice
+    68/push 0/imm32/end
+    68/push 0/imm32/start
+    89/<- %ecx 4/r32/esp
+    # var v/ebx: (handle var)
+    68/push 0/imm32
+    68/push 0/imm32
+    89/<- %ebx 4/r32/esp
+    # read function name
+    (next-mu-token *(ebp+8) %ecx)
+    # error checking
+    # if (word-slice == '{') abort
+    (slice-equal? %ecx "{")   # => eax
+    3d/compare-eax-and 0/imm32/false
+    0f 85/jump-if-!= $populate-mu-function-signature:error1/disp32
+    # if (word-slice == '->') abort
+    (slice-equal? %ecx "->")   # => eax
+    3d/compare-eax-and 0/imm32/false
+    0f 85/jump-if-!= $populate-mu-function-signature:error1/disp32
+    # if (word-slice == '}') abort
+    (slice-equal? %ecx "}")   # => eax
+    3d/compare-eax-and 0/imm32/false
+    0f 85/jump-if-!= $populate-mu-function-signature:error1/disp32
+    # save function name
+    (slice-to-string Heap %ecx %edi)  # Function-name
+    # save function inouts
+    {
+$populate-mu-function-signature:check-for-inout:
+      (next-mu-token *(ebp+8) %ecx)
+      (slice-empty? %ecx)  # => eax
+      3d/compare-eax-and 0/imm32/false
+      0f 85/jump-if-!= break/disp32
+      # if (word-slice == '->') break
+      (slice-equal? %ecx "->")   # => eax
+      3d/compare-eax-and 0/imm32/false
+      0f 85/jump-if-!= break/disp32
+      # if (word-slice == '{') abort
+      (slice-equal? %ecx "{")   # => eax
+      3d/compare-eax-and 0/imm32/false
+      0f 85/jump-if-!= $populate-mu-function-signature:error1/disp32
+      # if (word-slice == '}') abort
+      (slice-equal? %ecx "}")   # => eax
+      3d/compare-eax-and 0/imm32/false
+      0f 85/jump-if-!= $populate-mu-function-signature:error1/disp32
+      # v = parse-var-with-type(word-slice, first-line)
+      (parse-var-with-type %ecx *(ebp+8) %ebx *(ebp+0x10) *(ebp+0x14))
+      # assert(v->register == null)
+      # . eax: (addr var) = lookup(v)
+      (lookup *ebx *(ebx+4))  # => eax
+      81 7/subop/compare *(eax+0x18) 0/imm32  # Var-register
+      0f 85/jump-if-!= $populate-mu-function-signature:error2/disp32
+      # v->block-depth is implicitly 0
+      #
+      # out->inouts = append(v, out->inouts)
+      8d/copy-address *(edi+8) 0/r32/eax  # Function-inouts
+      (append-list Heap  *ebx *(ebx+4)  *(edi+8) *(edi+0xc)  %eax)  # Function-inouts, Function-inouts
+      #
+      e9/jump loop/disp32
+    }
+    # save function outputs
+    {
+$populate-mu-function-signature:check-for-out:
+      (next-mu-token *(ebp+8) %ecx)
+      (slice-empty? %ecx)  # => eax
+      3d/compare-eax-and 0/imm32/false
+      0f 85/jump-if-!= break/disp32
+      # if (word-slice == '{') abort
+      (slice-equal? %ecx "{")   # => eax
+      3d/compare-eax-and 0/imm32/false
+      0f 85/jump-if-!= $populate-mu-function-signature:error1/disp32
+      # if (word-slice == '->') abort
+      (slice-equal? %ecx "->")   # => eax
+      3d/compare-eax-and 0/imm32/false
+      0f 85/jump-if-!= $populate-mu-function-signature:error1/disp32
+      # if (word-slice == '}') abort
+      (slice-equal? %ecx "}")   # => eax
+      3d/compare-eax-and 0/imm32/false
+      0f 85/jump-if-!= $populate-mu-function-signature:error1/disp32
+      # v = parse-var-with-type(word-slice, first-line)
+      (parse-var-with-type %ecx *(ebp+8) %ebx *(ebp+0x10) *(ebp+0x14))
+      # assert(var->register != null)
+      # . eax: (addr var) = lookup(v)
+      (lookup *ebx *(ebx+4))  # => eax
+      81 7/subop/compare *(eax+0x18) 0/imm32  # Var-register
+      0f 84/jump-if-= $populate-mu-function-signature:error3/disp32
+      # out->outputs = append(v, out->outputs)
+      8d/copy-address *(edi+0x10) 0/r32/eax  # Function-outputs
+      (append-list Heap  *ebx *(ebx+4)  *(edi+0x10) *(edi+0x14)  %eax)  # Function-outputs, Function-outputs
+      #
+      e9/jump loop/disp32
+    }
+$populate-mu-function-signature:done:
+    (check-no-tokens-left *(ebp+8))
+$populate-mu-function-signature:end:
+    # . reclaim locals
+    81 0/subop/add %esp 0x10/imm32
+    # . restore registers
+    5f/pop-to-edi
+    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-function-signature:error1:
+    # error("function signature not in form 'fn <name> {'")
+    (write-buffered *(ebp+0x10) "function signature not in form 'fn <name> [inouts] [-> outputs] {' -- '")
+    (flush *(ebp+0x10))
+    (rewind-stream *(ebp+8))
+    (write-stream-data *(ebp+0x10) *(ebp+8))
+    (write-buffered *(ebp+0x10) "'\n")
+    (flush *(ebp+0x10))
+    (stop *(ebp+0x14) 1)
+    # never gets here
+
+$populate-mu-function-signature:error2:
+    # error("fn " fn ": function inout '" var "' cannot be in a register")
+    (write-buffered *(ebp+0x10) "fn ")
+    50/push-eax
+    (lookup *edi *(edi+4))  # Function-name Function-name => eax
+    (write-buffered *(ebp+0x10) %eax)
+    58/pop-to-eax
+    (write-buffered *(ebp+0x10) ": function inout '")
+    (lookup *eax *(eax+4))  # Var-name Var-name => eax
+    (write-buffered *(ebp+0x10) %eax)
+    (write-buffered *(ebp+0x10) "' cannot be in a register")
+    (flush *(ebp+0x10))
+    (stop *(ebp+0x14) 1)
+    # never gets here
+
+$populate-mu-function-signature:error3:
+    # error("fn " fn ": function output '" var "' must be in a register")
+    (write-buffered *(ebp+0x10) "fn ")
+    50/push-eax
+    (lookup *edi *(edi+4))  # Function-name Function-name => eax
+    (write-buffered *(ebp+0x10) %eax)
+    58/pop-to-eax
+    (write-buffered *(ebp+0x10) ": function output '")
+    (lookup *ebx *(ebx+4))  # => eax
+    (lookup *eax *(eax+4))  # Var-name Var-name => eax
+    (write-buffered *(ebp+0x10) %eax)
+    (write-buffered *(ebp+0x10) "' must be in a register, in instruction '")
+    (rewind-stream *(ebp+8))
+    (write-stream-data *(ebp+0x10) *(ebp+8))
+    (write-buffered *(ebp+0x10) "'\n")
+    (flush *(ebp+0x10))
+    (stop *(ebp+0x14) 1)
+    # never gets here
+
 test-function-header-with-arg:
     # . prologue
     55/push-ebp
@@ -10478,13 +10780,21 @@ check-mu-types:  # err: (addr buffered-file), ed: (addr exit-descriptor)
     89/<- %ebp 4/r32/esp
     # . save registers
     50/push-eax
-    # var curr/eax: (addr function) = *Program->functions
+    # var curr/eax: (addr function) = lookup(Program->functions)
     (lookup *_Program-functions *_Program-functions->payload)  # => eax
     {
 $check-mu-types:loop:
       # if (curr == null) break
       3d/compare-eax-and 0/imm32
       0f 84/jump-if-= break/disp32
+#?       # dump curr->name {{{
+#?       50/push-eax
+#?       (lookup *eax *(eax+4))  # Function-name Function-name => eax
+#?       (write-buffered Stderr %eax)
+#?       (write-buffered Stderr Newline)
+#?       (flush Stderr)
+#?       58/pop-to-eax
+#?       # }}}
       (check-mu-function %eax *(ebp+8) *(ebp+0xc))
       # curr = lookup(curr->next)
       (lookup *(eax+0x20) *(eax+0x24))  # Function-next Function-next => eax
@@ -10610,7 +10920,7 @@ check-mu-stmt:  # stmt: (addr stmt), fn: (addr function), err: (addr buffered-fi
     {
       74/jump-if-= break/disp8
       (check-mu-primitive *(ebp+8) *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))
-      eb/jump $check-mu-stmt:end/disp8
+      e9/jump $check-mu-stmt:end/disp32
     }
     # - otherwise find a function to check against
     # var f/eax: (addr function) = lookup(*Program->functions)
@@ -10622,7 +10932,17 @@ check-mu-stmt:  # stmt: (addr stmt), fn: (addr function), err: (addr buffered-fi
       (check-mu-call *(ebp+8) %eax *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))
       eb/jump $check-mu-stmt:end/disp8
     }
-    # TODO: error on unknown function. We need to first type-check calls to SubX functions.
+    # var f/eax: (addr function) = lookup(*Program->signatures)
+    (lookup *_Program-signatures *_Program-signatures->payload)  # => eax
+    (find-matching-function %eax *(ebp+8))  # => eax
+    3d/compare-eax-and 0/imm32
+    {
+      74/jump-if-= break/disp8
+      (check-mu-call *(ebp+8) %eax *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))
+      eb/jump $check-mu-stmt:end/disp8
+    }
+    # - otherwise abort
+    e9/jump $check-mu-stmt:unknown-call/disp32
 $check-mu-stmt:end:
     # . restore registers
     58/pop-to-eax
@@ -10631,6 +10951,16 @@ $check-mu-stmt:end:
     5d/pop-to-ebp
     c3/return
 
+$check-mu-stmt:unknown-call:
+    (write-buffered *(ebp+0x10) "unknown function '")
+    8b/-> *(ebp+8) 0/r32/eax
+    (lookup *(eax+4) *(eax+8))  # Stmt1-operation Stmt1-operation => eax
+    (write-buffered *(ebp+0x10) %eax)
+    (write-buffered *(ebp+0x10) "'\n")
+    (flush *(ebp+0x10))
+    (stop *(ebp+0x14) 1)
+    # never gets here
+
 has-primitive-name?:  # stmt: (addr stmt) -> result/eax: boolean
     # . prologue
     55/push-ebp
diff --git a/mu.vim b/mu.vim
index bbbaebec..afe7704b 100644
--- a/mu.vim
+++ b/mu.vim
@@ -53,7 +53,7 @@ syntax match muControl "\<loop\>\|\<loop-if[^ ]*"
 highlight link muControl PreProc
 
 syntax match muKeyword " -> "
-syntax keyword muKeyword fn type var
+syntax keyword muKeyword fn type var sig
 highlight link muKeyword PreProc
 
 syntax match muFunction "\(fn\s*\)\@<=\(\S\+\)"