diff options
author | Kartik Agaram <vc@akkartik.com> | 2020-02-17 23:35:44 -0800 |
---|---|---|
committer | Kartik Agaram <vc@akkartik.com> | 2020-02-18 00:07:11 -0800 |
commit | 01a28c56c77a265fc6dd9c29ba1f9abc04fbab7e (patch) | |
tree | 1e2602e6be4b7c812188c42a0cdf3eb428bdc2fd | |
parent | 75b9ff501037c788f7ae62e355d75909e477a83b (diff) | |
download | mu-01a28c56c77a265fc6dd9c29ba1f9abc04fbab7e.tar.gz |
6019 - finish supporting all branch primitives
I'd been thinking I didn't need unconditional `break` instructions, but I just realized that non-local unconditional breaks have a use. Stop over-thinking this, just support everything. The code is quite duplicated.
-rwxr-xr-x | apps/mu | bin | 133529 -> 144760 bytes | |||
-rw-r--r-- | apps/mu.subx | 239 | ||||
-rw-r--r-- | mu_instructions | 4 | ||||
-rw-r--r-- | mu_summary | 5 |
4 files changed, 238 insertions, 10 deletions
diff --git a/apps/mu b/apps/mu index d1a0d098..d19a17aa 100755 --- a/apps/mu +++ b/apps/mu Binary files differdiff --git a/apps/mu.subx b/apps/mu.subx index fa32de32..ee40b43c 100644 --- a/apps/mu.subx +++ b/apps/mu.subx @@ -1531,6 +1531,190 @@ test-convert-function-with-nonlocal-branches-and-loops-and-local-vars: 5d/pop-to-ebp c3/return +test-convert-function-with-nonlocal-unconditional-break-and-local-vars: + # . 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 {\n") + (write _test-input-stream " a: {\n") + (write _test-input-stream " var x: int\n") + (write _test-input-stream " {\n") + (write _test-input-stream " var y: int\n") + (write _test-input-stream " break a\n") + (write _test-input-stream " increment x\n") + (write _test-input-stream " }\n") + (write _test-input-stream " }\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-nonlocal-unconditional-break-and-local-vars/0") + (check-next-stream-line-equal _test-output-stream " # . prologue" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/1") + (check-next-stream-line-equal _test-output-stream " 55/push-ebp" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/2") + (check-next-stream-line-equal _test-output-stream " 89/<- %ebp 4/r32/esp" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/3") + (check-next-stream-line-equal _test-output-stream " {" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/4") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/5") + (check-next-stream-line-equal _test-output-stream " {" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/6") + (check-next-stream-line-equal _test-output-stream "a:loop:" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/7") + (check-next-stream-line-equal _test-output-stream " 68/push 0/imm32" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/8") + (check-next-stream-line-equal _test-output-stream " {" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/9") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:loop:" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/10") + (check-next-stream-line-equal _test-output-stream " 68/push 0/imm32" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/11") + (check-next-stream-line-equal _test-output-stream " 81 0/subop/add %esp 0x00000004/imm32" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/12") + (check-next-stream-line-equal _test-output-stream " 81 0/subop/add %esp 0x00000004/imm32" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/13") + (check-next-stream-line-equal _test-output-stream " e9/jump a:break/disp32" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/14") + (check-next-stream-line-equal _test-output-stream " }" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/15") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:break:" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/16") + (check-next-stream-line-equal _test-output-stream " 81 0/subop/add %esp 0x00000004/imm32" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/17") + (check-next-stream-line-equal _test-output-stream " }" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/18") + (check-next-stream-line-equal _test-output-stream "a:break:" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/19") + (check-next-stream-line-equal _test-output-stream " }" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/20") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/21") + (check-next-stream-line-equal _test-output-stream " # . epilogue" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/22") + (check-next-stream-line-equal _test-output-stream " 89/<- %esp 5/r32/ebp" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/23") + (check-next-stream-line-equal _test-output-stream " 5d/pop-to-ebp" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/24") + (check-next-stream-line-equal _test-output-stream " c3/return" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/25") + # . epilogue + 89/<- %esp 5/r32/ebp + 5d/pop-to-ebp + c3/return + +test-convert-function-with-unconditional-break-and-local-vars: + # . 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 {\n") + (write _test-input-stream " {\n") + (write _test-input-stream " var x: int\n") + (write _test-input-stream " {\n") + (write _test-input-stream " var y: int\n") + (write _test-input-stream " break\n") + (write _test-input-stream " increment x\n") + (write _test-input-stream " }\n") + (write _test-input-stream " }\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-unconditional-break-and-local-vars/0") + (check-next-stream-line-equal _test-output-stream " # . prologue" "F - test-convert-function-with-unconditional-break-and-local-vars/1") + (check-next-stream-line-equal _test-output-stream " 55/push-ebp" "F - test-convert-function-with-unconditional-break-and-local-vars/2") + (check-next-stream-line-equal _test-output-stream " 89/<- %ebp 4/r32/esp" "F - test-convert-function-with-unconditional-break-and-local-vars/3") + (check-next-stream-line-equal _test-output-stream " {" "F - test-convert-function-with-unconditional-break-and-local-vars/4") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:" "F - test-convert-function-with-unconditional-break-and-local-vars/5") + (check-next-stream-line-equal _test-output-stream " {" "F - test-convert-function-with-unconditional-break-and-local-vars/6") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:" "F - test-convert-function-with-unconditional-break-and-local-vars/7") + (check-next-stream-line-equal _test-output-stream " 68/push 0/imm32" "F - test-convert-function-with-unconditional-break-and-local-vars/8") + (check-next-stream-line-equal _test-output-stream " {" "F - test-convert-function-with-unconditional-break-and-local-vars/9") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:loop:" "F - test-convert-function-with-unconditional-break-and-local-vars/10") + (check-next-stream-line-equal _test-output-stream " 68/push 0/imm32" "F - test-convert-function-with-unconditional-break-and-local-vars/11") + (check-next-stream-line-equal _test-output-stream " 81 0/subop/add %esp 0x00000004/imm32" "F - test-convert-function-with-unconditional-break-and-local-vars/12") + (check-next-stream-line-equal _test-output-stream " }" "F - test-convert-function-with-unconditional-break-and-local-vars/13") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:break:" "F - test-convert-function-with-unconditional-break-and-local-vars/14") + (check-next-stream-line-equal _test-output-stream " 81 0/subop/add %esp 0x00000004/imm32" "F - test-convert-function-with-unconditional-break-and-local-vars/15") + (check-next-stream-line-equal _test-output-stream " }" "F - test-convert-function-with-unconditional-break-and-local-vars/16") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:" "F - test-convert-function-with-unconditional-break-and-local-vars/17") + (check-next-stream-line-equal _test-output-stream " }" "F - test-convert-function-with-unconditional-break-and-local-vars/18") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:" "F - test-convert-function-with-unconditional-break-and-local-vars/19") + (check-next-stream-line-equal _test-output-stream " # . epilogue" "F - test-convert-function-with-unconditional-break-and-local-vars/20") + (check-next-stream-line-equal _test-output-stream " 89/<- %esp 5/r32/ebp" "F - test-convert-function-with-unconditional-break-and-local-vars/21") + (check-next-stream-line-equal _test-output-stream " 5d/pop-to-ebp" "F - test-convert-function-with-unconditional-break-and-local-vars/22") + (check-next-stream-line-equal _test-output-stream " c3/return" "F - test-convert-function-with-unconditional-break-and-local-vars/23") + # . epilogue + 89/<- %esp 5/r32/ebp + 5d/pop-to-ebp + c3/return + +test-convert-function-with-nonlocal-unconditional-loop-and-local-vars: + # . 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 {\n") + (write _test-input-stream " a: {\n") + (write _test-input-stream " var x: int\n") + (write _test-input-stream " {\n") + (write _test-input-stream " var y: int\n") + (write _test-input-stream " loop a\n") + (write _test-input-stream " increment x\n") + (write _test-input-stream " }\n") + (write _test-input-stream " }\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-nonlocal-unconditional-loop-and-local-vars/0") + (check-next-stream-line-equal _test-output-stream " # . prologue" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/1") + (check-next-stream-line-equal _test-output-stream " 55/push-ebp" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/2") + (check-next-stream-line-equal _test-output-stream " 89/<- %ebp 4/r32/esp" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/3") + (check-next-stream-line-equal _test-output-stream " {" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/4") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/5") + (check-next-stream-line-equal _test-output-stream " {" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/6") + (check-next-stream-line-equal _test-output-stream "a:loop:" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/7") + (check-next-stream-line-equal _test-output-stream " 68/push 0/imm32" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/8") + (check-next-stream-line-equal _test-output-stream " {" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/9") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:loop:" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/10") + (check-next-stream-line-equal _test-output-stream " 68/push 0/imm32" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/11") + (check-next-stream-line-equal _test-output-stream " 81 0/subop/add %esp 0x00000004/imm32" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/12") + (check-next-stream-line-equal _test-output-stream " 81 0/subop/add %esp 0x00000004/imm32" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/13") + (check-next-stream-line-equal _test-output-stream " e9/jump a:loop/disp32" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/14") + (check-next-stream-line-equal _test-output-stream " }" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/15") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:break:" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/16") + (check-next-stream-line-equal _test-output-stream " 81 0/subop/add %esp 0x00000004/imm32" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/17") + (check-next-stream-line-equal _test-output-stream " }" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/18") + (check-next-stream-line-equal _test-output-stream "a:break:" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/19") + (check-next-stream-line-equal _test-output-stream " }" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/20") + (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/21") + (check-next-stream-line-equal _test-output-stream " # . epilogue" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/22") + (check-next-stream-line-equal _test-output-stream " 89/<- %esp 5/r32/ebp" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/23") + (check-next-stream-line-equal _test-output-stream " 5d/pop-to-ebp" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/24") + (check-next-stream-line-equal _test-output-stream " c3/return" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/25") + # . epilogue + 89/<- %esp 5/r32/ebp + 5d/pop-to-ebp + c3/return + ####################################################### # Parsing ####################################################### @@ -4518,32 +4702,74 @@ $emit-subx-stmt-list:branch-stmt-and-var-seen: (string-equal? *(ecx+4) "loop") # Stmt1-operation => eax 3d/compare-eax-and 0/imm32/false 0f 84/jump-if-= break/disp32 +$emit-subx-stmt-list:unconditional-loop: 81 7/subop/compare *(ecx+8) 0/imm32 # Stmt1-inouts # simple unconditional loops without a target {{{ { 0f 85/jump-if-!= break/disp32 +$emit-subx-stmt-list:zero-arg-unconditional-loop: (emit-cleanup-code-until-depth *(ebp+8) *(ebp+0x10) *Curr-block-depth) (emit-indent *(ebp+8) *Curr-block-depth) (write-buffered *(ebp+8) "e9/jump loop/disp32") (write-buffered *(ebp+8) Newline) - (clean-up-blocks *(ebp+0x10) *Curr-block-depth) - e9/jump $emit-subx-stmt-list:end/disp32 # skip remaining statements; they're dead code + e9/jump $emit-subx-stmt-list:cleanup/disp32 # skip remaining statements; they're dead code } # }}} # unconditional loops with a target {{{ { 0f 84/jump-if-= break/disp32 - # TODO +$emit-subx-stmt-list:unconditional-loop-with-target: + # var target/ebx: (addr array byte) = curr-stmt->inouts->value->name + 8b/-> *(ecx+8) 3/r32/ebx # Stmt1-inouts + 8b/-> *ebx 3/r32/ebx # List-value + 8b/-> *ebx 3/r32/ebx # Var-name + # clean up until target block + (emit-cleanup-code-until-target *(ebp+8) *(ebp+0x10) %ebx) + # emit jump to target block + (emit-indent *(ebp+8) *Curr-block-depth) + (write-buffered *(ebp+8) "e9/jump ") + (write-buffered *(ebp+8) %ebx) + (write-buffered *(ebp+8) ":loop/disp32\n") + # done + e9/jump $emit-subx-stmt-list:cleanup/disp32 } # }}} } # }}} + # unconditional breaks {{{ + { + # if (!string-equal?(var->operation, "break")) break + (string-equal? *(ecx+4) "break") # Stmt1-operation => eax + 3d/compare-eax-and 0/imm32/false + 0f 84/jump-if-= break/disp32 +$emit-subx-stmt-list:unconditional-break: + 81 7/subop/compare *(ecx+8) 0/imm32 # Stmt1-inouts + # simple unconditional breaks without a target + 0f 84/jump-if-= $emit-subx-stmt-list:emit-cleanup/disp32 # easy: just skip remaining statements + # unconditional breaks with a target {{{ +$emit-subx-stmt-list:unconditional-break-with-target: + # var target/ebx: (addr array byte) = curr-stmt->inouts->value->name + 8b/-> *(ecx+8) 3/r32/ebx # Stmt1-inouts + 8b/-> *ebx 3/r32/ebx # List-value + 8b/-> *ebx 3/r32/ebx # Var-name + # clean up until target block + (emit-cleanup-code-until-target *(ebp+8) *(ebp+0x10) %ebx) + # emit jump to target block + (emit-indent *(ebp+8) *Curr-block-depth) + (write-buffered *(ebp+8) "e9/jump ") + (write-buffered *(ebp+8) %ebx) + (write-buffered *(ebp+8) ":break/disp32\n") + # done + e9/jump $emit-subx-stmt-list:cleanup/disp32 + # }}} + } + # }}} # conditional branches {{{ # simple conditional branches without a target {{{ 81 7/subop/compare *(ecx+8) 0/imm32 # Stmt1-inouts { 0f 85/jump-if-!= break/disp32 -$emit-subx-stmt-list:zero-arg-branch: +$emit-subx-stmt-list:zero-arg-conditional-branch: # var old-block-depth/eax: int = Curr-block-depth - 1 8b/-> *Curr-block-depth 3/r32/ebx # cleanup prologue @@ -4579,7 +4805,7 @@ $emit-subx-stmt-list:zero-arg-branch: # conditional branches with an explicit target {{{ { 0f 84/jump-if-= break/disp32 -$emit-subx-stmt-list:branch-with-target: +$emit-subx-stmt-list:conditional-branch-with-target: # var target/ebx: (addr array byte) = curr-stmt->inouts->value->name 8b/-> *(ecx+8) 3/r32/ebx # Stmt1-inouts 8b/-> *ebx 3/r32/ebx # List-value @@ -4659,8 +4885,9 @@ $emit-subx-stmt-list:continue: 8b/-> *(esi+4) 6/r32/esi # List-next e9/jump loop/disp32 } -$emit-subx-stmt-list:cleanup: +$emit-subx-stmt-list:emit-cleanup: (emit-cleanup-code-until-depth *(ebp+8) *(ebp+0x10) *Curr-block-depth) +$emit-subx-stmt-list:cleanup: (clean-up-blocks *(ebp+0x10) *Curr-block-depth) $emit-subx-stmt-list:end: # . restore registers diff --git a/mu_instructions b/mu_instructions index 398a21e6..4d3c554b 100644 --- a/mu_instructions +++ b/mu_instructions @@ -184,7 +184,7 @@ Finally, unconditional jumps: loop {.name="loop", .subx-name="e9/jump loop/disp32"} loop label {.name="loop", .inouts=[label], .subx-name="e9/jump", .disp32=inouts[0] ":loop"} - -(So far it doesn't seem like unconditional breaks have much use.) +break {.name="break", .subx-name="e9/jump break/disp32"} +break label {.name="break", .inouts=[label], .subx-name="e9/jump", .disp32=inouts[0] ":break"} vim:ft=c:nowrap diff --git a/mu_summary b/mu_summary index 411656bb..2d48d2de 100644 --- a/mu_summary +++ b/mu_summary @@ -125,14 +125,15 @@ Jumps can take an optional label starting with '$': loop $foo This instruction jumps to the beginning of the block called $foo. It must lie -somewhere inside such a box. Jumps are only legal to containing blocks. Use +somewhere inside such a block. Jumps are only legal to containing blocks. Use named blocks with restraint; jumps to places far away can get confusing. There are two unconditional jumps: loop loop label - # unconditional break instructions don't seem useful + break + break label The remaining jump instructions are all conditional. Conditional jumps rely on the result of the most recently executed `compare` instruction. (To keep |