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.
This commit is contained in:
Kartik Agaram 2020-02-17 23:35:44 -08:00
parent 75b9ff5010
commit 01a28c56c7
4 changed files with 238 additions and 10 deletions

BIN
apps/mu

Binary file not shown.

View File

@ -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

View File

@ -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

View File

@ -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