# The Mu computer's level-2 language, also called Mu. # http://akkartik.name/post/mu-2019-2 # # To run: # $ ./ntranslate init.linux 0*.subx apps/mu.subx # == Goals # 1. Be memory safe. It should be impossible to corrupt the heap, or to create # a bad pointer. (Requires strong type safety.) # 2. Do as little as possible to achieve goal 1. # - runtime checks to avoid complex static analysis # - minimize impedance mismatch between source language and SubX target # == Language description # A program is a sequence of function definitions. # # Function example: # fn foo n: int -> result/eax: int { # ... # } # # Functions consist of a name, optional inputs, optional outputs and a block. # # Function inputs and outputs are variables. All variables have a type and # storage specifier. They can be placed either in memory (on the stack) or in # one of 6 named registers. # eax ecx edx ebx esi edi # Variables in registers must be primitive 32-bit types. # Variables not explicitly placed in a register are on the stack. # Variables in registers need not have a name; in that case you refer to them # directly by the register name. # # Function inputs are always passed in memory (on the stack), while outputs # are always returned in registers. # # Blocks mostly consist of statements. # # Statements mostly consist of a name, optional inputs and optional outputs. # # Statement inputs are variables or literals. Variables need to specify type # (and storage) the first time they're mentioned but not later. # # Statement outputs, like function outputs, must be variables in registers. # # Statement names must be either primitives or user-defined functions. # # Primitives can write to any register. # User-defined functions only write to hard-coded registers. Outputs of each # call must have the same registers as in the function definition. # # There are some other statement types: # - blocks. Multiple statements surrounded by '{...}' and optionally # prefixed with a label name and ':' # - { # ... # } # - foo: { # ... # } # # - variable definitions on the stack. E.g.: # - var foo: int # - var bar: (array int 3) # There's no initializer; variables are automatically initialized. # # - variables definitions in a register. E.g.: # - var foo/eax : int <- add bar 1 # The initializer is mandatory and must be a valid instruction that writes # a single output to the right register. In practice registers will # usually be either initialized by primitives or copied from eax. # - var eax : int <- foo bar quux # var floo/ecx : int <- copy eax # # Still todo: # global variables # heap allocations (planned name: 'handle') # user-defined types: 'type' for structs, 'choice' for unions # short-lived 'address' type for efficiently writing inside nested structs # # Formal types: # A program is a linked list of functions # A function contains: # name: string # inouts: linked list of vars <-- 'inouts' is more precise than 'inputs' # data: (address var) # next: (address list) # outputs: linked list of vars # data: (address var) # next: (address list) # body: block # A var-type contains: # name: string # type: s-expression of type ids # # A statement can be: # tag 0: a block # tag 1: a simple statement # tag 2: a variable defined on the stack # tag 3: a variable defined in a register # tag 4: a named block # # A block contains: # tag: 0 # statements: (address list statement) # # A regular statement contains: # tag: 1 # operation: string # inouts: (address list operand) # outputs: (address list var) # # A variable defined on the stack contains: # tag: 2 # name: string # type: type-tree # # A variable defined in a register contains: # tag: 3 # name: string # type: type-tree # reg: string # # A named block contains: # tag: 4 # name: string # statements: (address list statement) # == Translation: managing the stack # Now that we know what the language looks like in the large, let's think # about how translation happens from the bottom up. One crucial piece of the # puzzle is how Mu will clean up variables defined on the stack for you. # # Assume that we maintain a 'functions' list while parsing source code. And a # 'primitives' list is a global constant. Both these contain enough information # to perform type-checking on function calls or primitive statements, respectively. # # Defining variables pushes them on a stack with the current block depth and # enough information about their location (stack offset or register). # Starting a block increments the current block id. # Each statement now has enough information to emit code for it. # Ending a block is where the magic happens: # pop all variables at the current block depth # emit code to restore all register variables introduced at the current depth # emit code to clean up all stack variables at the current depth (just increment esp) # decrement the current block depth # # Formal types: # live-vars: stack of vars # var: # name: string # type: s-expression? Just a type id for now. # block: int # stack-offset: int (added to ebp) # register: string # either usual register names # or '*' to indicate any register # At most one of stack-offset or register-index must be non-zero. # A register of '*' designates a variable _template_. Only legal in formal # parameters for primitives. # == Translating a single function call # This one's easy. Assuming we've already checked things, we just drop the # outputs (which use hard-coded registers) and emit inputs in a standard format. # # out1, out2, out3, ... <- name inout1, inout2, inout3, ... # => # (subx-name inout1 inout2 inout3) # # Formal types: # functions: linked list of info # name: string # inouts: linked list of vars # outputs: linked list of vars # body: block (singleton linked list) # subx-name: string # == Translating a single primitive instruction # A second crucial piece of the puzzle is how Mu converts fairly regular # primitives with their uniform syntax to SubX instructions with their gnarly # x86 details. # # Mu instructions have inputs and outputs. Primitives can have up to 2 of # them. # SubX instructions have rm32 and r32 operands. # The translation between them covers almost all the possibilities. # Instructions with 1 inout may turn into ones with 1 rm32 # (e.g. incrementing a var on the stack) # Instructions with 1 output may turn into ones with 1 rm32 # (e.g. incrementing a var in a register) # 1 inout and 1 output may turn into 1 rm32 and 1 r32 # (e.g. adding a var to a reg) # 2 inouts may turn into 1 rm32 and 1 r32 # (e.g. adding a reg to a var) # 1 inout and 1 literal may turn into 1 rm32 and 1 imm32 # (e.g. adding a constant to a var) # 1 output and 1 literal may turn into 1 rm32 and 1 imm32 # (e.g. adding a constant to a reg) # 2 outputs to hardcoded registers and 1 inout may turn into 1 rm32 # (special-case: divide edx:eax by a var or reg) # Observations: # We always emit rm32. It may be the first inout or the first output. # We may emit r32 or imm32 or neither. # When we emit r32 it may come from first inout or second inout or first output. # # Accordingly, the formal data structure for a primitive looks like this: # primitives: linked list of info # name: string # mu-inouts: linked list of vars to check # mu-outputs: linked list of vars to check # subx-name: string # subx-rm32: enum arg-location # subx-r32: enum arg-location # subx-imm32: enum arg-location # arg-location: enum # 0 means none # 1 means first inout # 2 means second inout # 3 means first output # == Translating a block # Emit block name if necessary # Emit '{' # When you encounter a statement, emit it as above # When you encounter a variable declaration # emit any code needed for it (bzeros) # push it on the var stack # update register dict if necessary # When you encounter '}' # While popping variables off the var stack until block id changes # Emit code needed to clean up the stack # either increment esp # or pop into appropriate register # The rest is straightforward. == data Program: # (address function) 0/imm32 Function-name: 0/imm32 Function-subx-name: 4/imm32 Function-inouts: # (address list var) 8/imm32 Function-outputs: # (address list var) 0xc/imm32 Function-body: # (address block) 0x10/imm32 Function-next: # (address function) 0x14/imm32 Function-size: 0x18/imm32/24 Primitive-name: 0/imm32 Primitive-inouts: # (address list var) 4/imm32 Primitive-outputs: # (address list var) 8/imm32 Primitive-subx-name: # (address string) 0xc/imm32 Primitive-subx-rm32: # enum arg-location 0x10/imm32 Primitive-subx-r32: # enum arg-location 0x14/imm32 Primitive-subx-imm32: # enum arg-location 0x18/imm32 Primitive-next: # (address function) 0x1c/imm32 Primitive-size: 0x20/imm32/24 Stmt-tag: 0/imm32 Block-statements: # (address list statement) 4/imm32 Stmt1-operation: # string 4/imm32 Stmt1-inouts: # (address list var) 8/imm32 Stmt1-outputs: # (address list var) 0xc/imm32 Vardef-name: # string 4/imm32 Vardef-type: # (address tree type-id) 8/imm32 Regvardef-name: # string 4/imm32 Regvardef-type: # (address tree type-id) 8/imm32 Regvardef-register: # string 0xc/imm32 Named-block-name: 4/imm32 Named-block-statements: # (address list statement) 8/imm32 Stmt-size: 0x10/imm32 Var-name: 0/imm32 Var-type: 4/imm32 Var-block: 8/imm32 Var-stack-offset: 0xc/imm32 Var-register: 0x10/imm32 Var-size: 0x14/imm32 Any-register: # "*" # size 1/imm32 # data 2a/asterisk List-value: 0/imm32 List-next: 4/imm32 List-size: 8/imm32 == code Entry: # . prologue 89/<- %ebp 4/r32/esp (new-segment Heap-size Heap) # if (argv[1] == "test') run-tests() { # if (argc <= 1) break 81 7/subop/compare *ebp 1/imm32 7e/jump-if-lesser-or-equal break/disp8 # if (argv[1] != "test") break (kernel-string-equal? *(ebp+8) "test") # => eax 3d/compare-eax-and 0/imm32 74/jump-if-equal break/disp8 # (run-tests) # syscall(exit, *Num-test-failures) 8b/-> *Num-test-failures 3/r32/ebx eb/jump $mu-main:end/disp8 } # otherwise convert Stdin (convert-mu Stdin Stdout) (flush Stdout) # syscall(exit, 0) bb/copy-to-ebx 0/imm32 $mu-main:end: b8/copy-to-eax 1/imm32/exit cd/syscall 0x80/imm8 convert-mu: # in : (address buffered-file), out : (address buffered-file) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (parse-mu *(ebp+8)) (check-mu-types) (emit-subx *(ebp+0xc)) $convert-mu:end: # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-convert-empty-input: # empty input => empty output # . 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) # (convert-mu _test-input-buffered-file _test-output-buffered-file) (flush _test-output-buffered-file) (check-stream-equal _test-output-stream "" "F - test-convert-empty-input") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-convert-function-skeleton: # empty function decl => function prologue and epilogue # fn foo { # } # => # foo: # # . prologue # 55/push-ebp # 89/<- %ebp 4/r32/esp # # . epilogue # 89/<- %esp 5/r32/ebp # 5d/pop-to-ebp # c3/return # . 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 foo {\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-skeleton/0") (check-next-stream-line-equal _test-output-stream "# . prologue" "F - test-convert-function-skeleton/1") (check-next-stream-line-equal _test-output-stream "55/push-ebp" "F - test-convert-function-skeleton/2") (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp" "F - test-convert-function-skeleton/3") (check-next-stream-line-equal _test-output-stream "# . epilogue" "F - test-convert-function-skeleton/4") (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp" "F - test-convert-function-skeleton/5") (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp" "F - test-convert-function-skeleton/6") (check-next-stream-line-equal _test-output-stream "c3/return" "F - test-convert-function-skeleton/7") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-convert-multiple-function-skeletons: # multiple functions correctly organized into a linked list # fn foo { # } # fn bar { # } # => # foo: # # . prologue # 55/push-ebp # 89/<- %ebp 4/r32/esp # # . epilogue # 89/<- %esp 5/r32/ebp # 5d/pop-to-ebp # c3/return # bar: # # . prologue # 55/push-ebp # 89/<- %ebp 4/r32/esp # # . epilogue # 89/<- %esp 5/r32/ebp # 5d/pop-to-ebp # c3/return # . 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 foo {\n") (write _test-input-stream "}\n") (write _test-input-stream "fn bar {\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 first function (check-next-stream-line-equal _test-output-stream "foo:" "F - test-convert-multiple-function-skeletons/0") (check-next-stream-line-equal _test-output-stream "# . prologue" "F - test-convert-multiple-function-skeletons/1") (check-next-stream-line-equal _test-output-stream "55/push-ebp" "F - test-convert-multiple-function-skeletons/2") (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp" "F - test-convert-multiple-function-skeletons/3") (check-next-stream-line-equal _test-output-stream "# . epilogue" "F - test-convert-multiple-function-skeletons/4") (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp" "F - test-convert-multiple-function-skeletons/5") (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp" "F - test-convert-multiple-function-skeletons/6") (check-next-stream-line-equal _test-output-stream "c3/return" "F - test-convert-multiple-function-skeletons/7") # check second function (check-next-stream-line-equal _test-output-stream "bar:" "F - test-convert-multiple-function-skeletons/10") (check-next-stream-line-equal _test-output-stream "# . prologue" "F - test-convert-multiple-function-skeletons/11") (check-next-stream-line-equal _test-output-stream "55/push-ebp" "F - test-convert-multiple-function-skeletons/12") (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp" "F - test-convert-multiple-function-skeletons/13") (check-next-stream-line-equal _test-output-stream "# . epilogue" "F - test-convert-multiple-function-skeletons/14") (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp" "F - test-convert-multiple-function-skeletons/15") (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp" "F - test-convert-multiple-function-skeletons/16") (check-next-stream-line-equal _test-output-stream "c3/return" "F - test-convert-multiple-function-skeletons/17") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-convert-function-with-arg: # function with one arg # fn foo n : int { # } # => # foo: # # . prologue # 55/push-ebp # 89/<- %ebp 4/r32/esp # # . epilogue # 89/<- %esp 5/r32/ebp # 5d/pop-to-ebp # c3/return # . 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 foo n : 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-skeleton/0") (check-next-stream-line-equal _test-output-stream "# . prologue" "F - test-convert-function-skeleton/1") (check-next-stream-line-equal _test-output-stream "55/push-ebp" "F - test-convert-function-skeleton/2") (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp" "F - test-convert-function-skeleton/3") (check-next-stream-line-equal _test-output-stream "# . epilogue" "F - test-convert-function-skeleton/4") (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp" "F - test-convert-function-skeleton/5") (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp" "F - test-convert-function-skeleton/6") (check-next-stream-line-equal _test-output-stream "c3/return" "F - test-convert-function-skeleton/7") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return ####################################################### # Parsing ####################################################### parse-mu: # in : (address buffered-file) # pseudocode # var curr-function = Program # var line : (stream byte 512) # var word-slice : slice # while true # line loop # clear-stream(line) # read-line-buffered(in, line) # if (line->write == 0) break # end of file # while true # word loop # word-slice = next-word-or-string(line) # if slice-empty?(word-slice) # end of line # break # else if slice-starts-with?(word-slice, "#") # comment # break # end of line # else if slice-equal(word-slice, "fn") # var new-function : (address function) = new function # populate-mu-function-header(in, new-function) # populate-mu-function-body(in, new-function) # *curr-function = new-function # curr-function = &new-function->next # else # abort() # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 51/push-ecx 52/push-edx 57/push-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-function/edi : (address function) = Program bf/copy-to-edi Program/imm32 { $parse-mu:line-loop: (clear-stream %ecx) (read-line-buffered *(ebp+8) %ecx) # if (line->write == 0) break 81 7/subop/compare *ecx 0/imm32 0f 84/jump-if-equal break/disp32 #? # dump line {{{ #? (write 2 "line: ^") #? (write-stream 2 %ecx) #? (write 2 "$\n") #? (rewind-stream %ecx) #? # }}} { # word loop $parse-mu:word-loop: (next-word-or-string %ecx %edx) # if slice-empty?(word-slice) break (slice-empty? %edx) 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal break/disp32 # if (*word-slice->start == "#") break # . eax = *word-slice->start 8b/-> *edx 0/r32/eax 8a/copy-byte *eax 0/r32/AL 81 4/subop/and %eax 0xff/imm32 # . if (eax == '#') break 3d/compare-eax-and 0x23/imm32/hash 0f 84/jump-if-equal break/disp32 # if (slice-equal?(word-slice, "fn")) parse a function { (slice-equal? %edx "fn") 3d/compare-eax-and 0/imm32 0f 84/jump-if-equal break/disp32 # var new-function/eax : (address function) = populate-mu-function() (allocate Heap *Function-size) # => eax (populate-mu-function-header %ecx %eax) (populate-mu-function-body *(ebp+8) %eax) # *curr-function = new-function 89/<- *edi 0/r32/eax # curr-function = &new-function->next 8d/address-> *(eax+0x14) 7/r32/edi # Function-next e9/jump $parse-mu:word-loop/disp32 } # otherwise abort e9/jump $parse-mu:abort/disp32 } # end word loop e9/jump loop/disp32 } # end line loop $parse-mu:end: # . reclaim locals 81 0/subop/add %esp 0x214/imm32 # . restore registers 5f/pop-to-edi 5a/pop-to-edx 59/pop-to-ecx 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return $parse-mu:abort: # error("unexpected top-level command: " word-slice "\n") (write-buffered Stderr "unexpected top-level command: ") (write-buffered Stderr %edx) (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 # scenarios considered: # ✗ fn foo # no block # ✓ fn foo { # ✗ fn foo { { # ✗ fn foo { } # ✗ fn foo { } { # ✗ fn foo x { # ✗ fn foo x : { # ✓ fn foo x : int { # ✓ fn foo x: int { # ✓ fn foo x: int -> y/eax: int { populate-mu-function-header: # first-line : (address stream byte), out : (address function) # pseudocode: # var name : slice = next-word(first-line) # assert(name not in '{' '}' '->') # out->name = slice-to-string(name) # ## inouts # while true # ## name # name = next-word(first-line) # if (name == '{') goto done # if (name == '->') break # assert(name != '}') # var v : (address var) = parse-var-with-type(name, first-line) # out->inouts = append(out->inouts, v) # ## outputs # while true # ## name # name = next-word(first-line) # assert(name not in '{' '}' '->') # var v : (address var) = parse-var-with-type(name, first-line) # out->outputs = append(out->outputs, v) # done: # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 51/push-ecx 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 # read function name (next-word *(ebp+8) %ecx) # error checking # if (word-slice == '{') abort (slice-equal? %ecx "{") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32 # if (word-slice == '->') abort (slice-equal? %ecx "->") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32 # if (word-slice == '}') abort (slice-equal? %ecx "}") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32 # save function name (slice-to-string Heap %ecx) # => eax 89/<- *edi 0/r32/eax # Function-name # save function inouts { (next-word *(ebp+8) %ecx) # if (word-slice == '{') goto done (slice-equal? %ecx "{") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $populate-mu-function-header:done/disp32 # if (word-slice == '->') break (slice-equal? %ecx "->") # => eax 3d/compare-eax-and 0/imm32 75/jump-if-not-equal break/disp8 # if (word-slice == '}') abort (slice-equal? %ecx "}") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32 # (parse-var-with-type %ecx *(ebp+8)) (append-list Heap %eax *(edi+8)) # Function-inouts 89/<- *(edi+8) 0/r32/eax # Function-inouts e9/jump loop/disp32 } # save function outputs { (next-word *(ebp+8) %ecx) # if (word-slice == '{') break (slice-equal? %ecx "{") # => eax 3d/compare-eax-and 0/imm32 75/jump-if-not-equal break/disp8 # if (word-slice == '->') abort (slice-equal? %ecx "->") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32 # if (word-slice == '}') abort (slice-equal? %ecx "}") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $populate-mu-function-header:abort/disp32 # (parse-var-with-type %ecx *(ebp+8)) (append-list Heap %eax *(edi+0xc)) # Function-outputs 89/<- *(edi+0xc) 0/r32/eax # Function-outputs e9/jump loop/disp32 } $populate-mu-function-header:done: # assert that there's no further token { # word-slice = next-word(line) (next-word *(ebp+8) %ecx) # if (word-slice == '') break (slice-empty? %ecx) 3d/compare-eax-and 0/imm32 75/jump-if-not-equal break/disp8 # if (slice-starts-with?(word-slice, "#")) break # . eax = *word-slice->start 8b/-> *ecx 0/r32/eax 8a/copy-byte *eax 0/r32/AL 81 4/subop/and %eax 0xff/imm32 # . if (eax == '#') break 3d/compare-eax-and 0x23/imm32/hash 74/jump-if-equal break/disp8 # otherwise abort eb/jump $populate-mu-function-header:abort/disp8 } $populate-mu-function-header:end: # . reclaim locals 81 0/subop/add %esp 8/imm32 # . restore registers 5f/pop-to-edi 59/pop-to-ecx 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return $populate-mu-function-header:abort: # error("function header not in form 'fn {'") (write-buffered Stderr "function header not in form 'fn [inouts] [-> outputs] {' -- '") (flush Stderr) (rewind-stream *(ebp+8)) (write-stream 2 *(ebp+8)) (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 test-function-header-with-arg: # 'foo n : int {' # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-input-stream) (write _test-input-stream "foo n : int {\n") # result/ecx : (address function) 2b/subtract-> *Function-size 4/r32/esp 89/<- %ecx 4/r32/esp (zero-out %ecx *Function-size) # convert (populate-mu-function-header _test-input-stream %ecx) # check result (check-strings-equal *ecx "foo" "F - test-function-header-with-arg/name") # Function-name # edx : (address list var) = result->inouts 8b/-> *(ecx+8) 2/r32/edx # Function-inouts # ebx : (address var) = result->inouts->value 8b/-> *edx 3/r32/ebx # List-value (check-strings-equal *ebx "n" "F - test-function-header-with-arg/inout:0") # Var-name (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:0/type") # Var-type # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-function-header-with-multiple-args: # 'fn foo a: int, b: int, c: int {' # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-input-stream) (write _test-input-stream "foo a: int, b: int c: int {\n") # result/ecx : (address function) 2b/subtract-> *Function-size 4/r32/esp 89/<- %ecx 4/r32/esp (zero-out %ecx *Function-size) # convert (populate-mu-function-header _test-input-stream %ecx) # check result (check-strings-equal *ecx "foo") # Function-name # edx : (address list var) = result->inouts 8b/-> *(ecx+8) 2/r32/edx # Function-inouts # ebx : (address var) = result->inouts->value 8b/-> *edx 3/r32/ebx # List-value (check-strings-equal *ebx "a" "F - test-function-header-with-multiple-args/inout:0") # Var-name (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:0/type") # Var-type # edx = result->inouts->next 8b/-> *(edx+4) 2/r32/edx # List-next # ebx = result->inouts->next->value 8b/-> *edx 3/r32/ebx # List-value (check-strings-equal *ebx "b" "F - test-function-header-with-multiple-args/inout:1") # Var-name (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:1/type") # Var-type # edx = result->inouts->next->next 8b/-> *(edx+4) 2/r32/edx # List-next # ebx = result->inouts->next->next->value 8b/-> *edx 3/r32/ebx # List-value (check-strings-equal *ebx "c" "F - test-function-header-with-multiple-args/inout:2") # Var-name (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:2/type") # Var-type # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-function-with-multiple-args-and-outputs: # fn foo a: int, b: int, c: int -> x: int, y: int { # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-input-stream) (write _test-input-stream "foo a: int, b: int, c: int -> x/ecx: int y/edx : int {\n") # result/ecx : (address function) 2b/subtract-> *Function-size 4/r32/esp 89/<- %ecx 4/r32/esp (zero-out %ecx *Function-size) # convert (populate-mu-function-header _test-input-stream %ecx) # check result (check-strings-equal *ecx "foo") # Function-name # edx : (address list var) = result->inouts 8b/-> *(ecx+8) 2/r32/edx # Function-inouts # ebx : (address var) = result->inouts->value 8b/-> *edx 3/r32/ebx # List-value (check-strings-equal *ebx "a" "F - test-function-header-with-multiple-args/inout:0") # Var-name (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:0/type") # Var-type # edx = result->inouts->next 8b/-> *(edx+4) 2/r32/edx # List-next # ebx = result->inouts->next->value 8b/-> *edx 3/r32/ebx # List-value (check-strings-equal *ebx "b" "F - test-function-header-with-multiple-args/inout:1") # Var-name (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:1/type") # Var-type # edx = result->inouts->next->next 8b/-> *(edx+4) 2/r32/edx # List-next # ebx = result->inouts->next->next->value 8b/-> *edx 3/r32/ebx # List-value (check-strings-equal *ebx "c" "F - test-function-header-with-multiple-args/inout:2") # Var-name (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/inout:2/type") # Var-type # edx : (address list var) = result->outputs 8b/-> *(ecx+0xc) 2/r32/edx # Function-outputs # ebx : (address var) = result->outputs->value 8b/-> *edx 3/r32/ebx # List-value (check-strings-equal *ebx "x" "F - test-function-header-with-multiple-args/output:0") # Var-name (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/output:0/type") # Var-type (check-strings-equal *(ebx+0x10) "ecx" "F - test-function-header-with-arg/output:0/register") # Var-register # edx = result->outputs->next 8b/-> *(edx+4) 2/r32/edx # List-next # ebx = result->outputs->next->value 8b/-> *edx 3/r32/ebx # List-value (check-strings-equal *ebx "y" "F - test-function-header-with-multiple-args/output:1") # Var-name (check-ints-equal *(ebx+4) 1 "F - test-function-header-with-arg/output:1/type") # Var-type (check-strings-equal *(ebx+0x10) "edx" "F - test-function-header-with-arg/output:0/register") # Var-register # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return # format for variables with types # x : int # x: int # x: int, # ignores at most one trailing colon or comma parse-var-with-type: # name: slice, first-line: (address stream) -> result/eax: (address var) # pseudocode: # var v : (address var) = allocate(Heap, Var-size) # var s : slice # next-token-from-slice(name->start, name->end, '/', s) # var end : address = s->end # if (slice-ends-with(s, ":")) # decrement s->end # if (slice-ends-with(s, ",")) # decrement s->end # v->name = slice-to-string(s) # ## register # next-token-from-slice(end, name->end, '/', s) # if (slice-ends-with(s, ":")) # decrement s->end # if (slice-ends-with(s, ",")) # decrement s->end # if (!slice-empty?(s)) # v->register = slice-to-string(s) # ## type # s = next-mu-token(first-line) # assert(s not in '{' '}' '->') # if (slice-empty?(s)) { # s = next-mu-token(first-line) # assert(type not in '{' '}' '->') # } # type = type-for(s) # v->type = type # return v # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx 52/push-edx 53/push-ebx 56/push-esi 57/push-edi # var result/edi : (address var) = allocate(Heap, Var-size) (allocate Heap *Var-size) 89/<- %edi 0/r32/eax # esi = name 8b/-> *(ebp+8) 6/r32/esi # var s/ecx : slice 68/push 0/imm32/end 68/push 0/imm32/start 89/<- %ecx 4/r32/esp # save v->name (next-token-from-slice *esi *(esi+4) 0x2f %ecx) # Slice-start, Slice-end, '/' # . end/edx = s->end 8b/-> *(ecx+4) 2/r32/edx # . if s ends with ':', decrement s->end { 8b/-> *(ecx+4) 0/r32/eax 48/decrement-eax 8a/copy-byte *eax 3/r32/BL 81 4/subop/and %ebx 0xff/imm32 81 7/subop/compare %ebx 0x3a/imm32/colon 75/jump-if-not-equal break/disp8 89/<- *(ecx+4) 0/r32/eax } # . if s ends with ',', decrement s->end { 8b/-> *(ecx+4) 0/r32/eax 48/decrement-eax 8a/copy-byte *eax 3/r32/BL 81 4/subop/and %ebx 0xff/imm32 81 7/subop/compare %ebx 0x2c/imm32/comma 75/jump-if-not-equal break/disp8 89/<- *(ecx+4) 0/r32/eax } (slice-to-string Heap %ecx) # => eax 89/<- *edi 0/r32/eax # Var-name # save v->register (next-token-from-slice %edx *(esi+4) 0x2f %ecx) # end, name->end, '/' # . if s ends with ':', decrement s->end { 8b/-> *(ecx+4) 0/r32/eax 48/decrement-eax 8a/copy-byte *eax 3/r32/BL 81 4/subop/and %ebx 0xff/imm32 81 7/subop/compare %ebx 0x3a/imm32/colon 75/jump-if-not-equal break/disp8 89/<- *(ecx+4) 0/r32/eax } # . if s ends with ',', decrement s->end { 8b/-> *(ecx+4) 0/r32/eax 48/decrement-eax 8a/copy-byte *eax 3/r32/BL 81 4/subop/and %ebx 0xff/imm32 81 7/subop/compare %ebx 0x2c/imm32/comma 75/jump-if-not-equal break/disp8 89/<- *(ecx+4) 0/r32/eax } # if (!slice-empty?(s)) v->register = slice-to-string(s) { (slice-empty? %ecx) 3d/compare-eax-and 0/imm32 75/jump-if-not-equal break/disp8 (slice-to-string Heap %ecx) 89/<- *(edi+0x10) 0/r32/eax # Var-register } # save v->type (next-mu-token *(ebp+0xc) %ecx) # if (word-slice == '{') abort (slice-equal? %ecx "{") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32 # if (word-slice == '->') abort (slice-equal? %ecx "->") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32 # if (word-slice == '}') abort (slice-equal? %ecx "}") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32 # if (slice-empty?(type)) skip (slice-empty? %ecx) { 3d/compare-eax-and 0/imm32 0f 84/jump-if-equal break/disp32 (next-mu-token *(ebp+0xc) %ecx) # if (word-slice == '{') abort (slice-equal? %ecx "{") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32 # if (word-slice == '->') abort (slice-equal? %ecx "->") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32 # if (word-slice == '}') abort (slice-equal? %ecx "}") # => eax 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32 } (type-for %ecx) 89/<- *(edi+4) 0/r32/eax # Var-type $parse-var-with-type:end: # return result 89/<- %eax 7/r32/edi # . reclaim locals 81 0/subop/add %esp 8/imm32 # . restore registers 5f/pop-to-edi 5e/pop-to-esi 5b/pop-to-ebx 5a/pop-to-edx 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return $parse-var-with-type:abort: # error("function header not in form 'fn {'") (write-buffered Stderr "var should have form 'name: type' in '") (flush Stderr) (rewind-stream *(ebp+0xc)) (write-stream 2 *(ebp+0xc)) (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 next-mu-token: # in: (address stream), out: (address slice) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 57/push-edi # edi = out 8b/-> *(ebp+0xc) 7/r32/edi # (next-word *(ebp+8) %edi) # TODO: support s-expressions # if out ends with ':', decrement out->end { 8b/-> *(edi+4) 0/r32/eax 48/decrement-eax 8a/copy-byte *eax 3/r32/BL 81 4/subop/and %ebx 0xff/imm32 81 7/subop/compare %ebx 0x3a/imm32/colon 75/jump-if-not-equal break/disp8 89/<- *(edi+4) 0/r32/eax } # if out ends with ',', decrement out->end { 8b/-> *(edi+4) 0/r32/eax 48/decrement-eax 8a/copy-byte *eax 3/r32/BL 81 4/subop/and %ebx 0xff/imm32 81 7/subop/compare %ebx 0x2c/imm32/comma 75/jump-if-not-equal break/disp8 89/<- *(edi+4) 0/r32/eax } $next-mu-token:end: b8/copy-to-eax 1/imm32/int # . restore registers 5f/pop-to-edi 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return type-for: # name: (address slice) -> result/eax: type-tree # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers #? (write-buffered Stderr "type: ") #? (write-slice-buffered Stderr *(ebp+8)) #? (write-buffered Stderr Newline) #? (flush Stderr) $type-for:end: b8/copy-to-eax 1/imm32/int # . restore registers # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-parse-var-with-type: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "x:" b8/copy-to-eax "x:"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # _test-input-stream contains "int" (clear-stream _test-input-stream) (write _test-input-stream "int") # (parse-var-with-type %ecx _test-input-stream) 8b/-> *eax 2/r32/edx # Var-name (check-strings-equal %edx "x" "F - test-var-with-type/name") 8b/-> *(eax+4) 2/r32/edx # Var-type (check-ints-equal %edx 1 "F - test-var-with-type/type") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-parse-var-with-type-and-register: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "x/eax" b8/copy-to-eax "x/eax"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # _test-input-stream contains ": int" (clear-stream _test-input-stream) (write _test-input-stream ": int") # (parse-var-with-type %ecx _test-input-stream) 8b/-> *eax 2/r32/edx # Var-name (check-strings-equal %edx "x" "F - test-var-with-type-and-register/name") 8b/-> *(eax+0x10) 2/r32/edx # Var-register (check-strings-equal %edx "eax" "F - test-var-with-type-and-register/register") 8b/-> *(eax+4) 2/r32/edx # Var-type (check-ints-equal %edx 1 "F - test-var-with-type-and-register/type") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-parse-var-with-trailing-characters: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "x/eax:" b8/copy-to-eax "x/eax:"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # _test-input-stream contains "int," (clear-stream _test-input-stream) (write _test-input-stream "int,") # (parse-var-with-type %ecx _test-input-stream) 8b/-> *eax 2/r32/edx # Var-name (check-strings-equal %edx "x" "F - test-var-with-trailing-characters/name") 8b/-> *(eax+0x10) 2/r32/edx # Var-register (check-strings-equal %edx "eax" "F - test-var-with-trailing-characters/register") 8b/-> *(eax+4) 2/r32/edx # Var-type (check-ints-equal %edx 1 "F - test-var-with-trailing-characters/type") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return # identifier starts with a letter or '$' or '_' # no constraints at the moment on later letters # all we really want to do so far is exclude '{', '}' and '->' is-identifier?: # in : (address slice) -> result/eax : boolean # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # if (slice-empty?(in)) return false (slice-empty? *(ebp+8)) # => eax 3d/compare-eax-and 0/imm32 75/jump-if-not-equal $is-identifier?:false/disp8 # var c/eax : char = *in->start 8b/-> *(ebp+8) 0/r32/eax 8b/-> *eax 0/r32/eax 8a/copy-byte *eax 0/r32/AL 81 4/subop/and %eax 0xff/imm32 # if (c == '$') return true 3d/compare-eax-and 0x24/imm32/$ 74/jump-if-equal $is-identifier?:true/disp8 # if (c == '_') return true 3d/compare-eax-and 0x5f/imm32/_ 74/jump-if-equal $is-identifier?:true/disp8 # drop case 25/and-eax-with 0x5f/imm32 # if (c < 'A') return false 3d/compare-eax-and 0x41/imm32/A 7c/jump-if-lesser $is-identifier?:false/disp8 # if (c > 'Z') return false 3d/compare-eax-and 0x5a/imm32/Z 7f/jump-if-greater $is-identifier?:false/disp8 # otherwise return true $is-identifier?:true: b8/copy-to-eax 1/imm32/true eb/jump $is-identifier?:end/disp8 $is-identifier?:false: b8/copy-to-eax 0/imm32/false $is-identifier?:end: # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-dollar: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "$a" b8/copy-to-eax "$a"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 1 "F - test-is-identifier-dollar") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-underscore: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "_a" b8/copy-to-eax "_a"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 1 "F - test-is-identifier-underscore") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-a: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "a$" b8/copy-to-eax "a$"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 1 "F - test-is-identifier-a") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-z: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "z$" b8/copy-to-eax "z$"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 1 "F - test-is-identifier-z") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-A: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "A$" b8/copy-to-eax "A$"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 1 "F - test-is-identifier-A") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-Z: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "Z$" b8/copy-to-eax "Z$"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 1 "F - test-is-identifier-Z") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-@: # character before 'A' is invalid # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "@a" b8/copy-to-eax "@a"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 0 "F - test-is-identifier-@") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-square-bracket: # character after 'Z' is invalid # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "[a" b8/copy-to-eax "[a"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 0 "F - test-is-identifier-@") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-backtick: # character before 'a' is invalid # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "`a" b8/copy-to-eax "`a"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 0 "F - test-is-identifier-backtick") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-curly-brace-open: # character after 'z' is invalid; also used for blocks # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "{a" b8/copy-to-eax "{a"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 0 "F - test-is-identifier-curly-brace-open") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-curly-brace-close: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "}a" b8/copy-to-eax "}a"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 0 "F - test-is-identifier-curly-brace-close") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-is-identifier-hyphen: # disallow leading '-' since '->' has special meaning # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (eax..ecx) = "-a" b8/copy-to-eax "-a"/imm32 8b/-> *eax 1/r32/ecx 8d/copy-address *(eax+ecx+4) 1/r32/ecx 05/add-to-eax 4/imm32 # var slice/ecx = {eax, ecx} 51/push-ecx 50/push-eax 89/<- %ecx 4/r32/esp # (is-identifier? %ecx) (check-ints-equal %eax 0 "F - test-is-identifier-hyphen") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return populate-mu-function-body: # in : (address buffered-file), out : (address function) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 56/push-esi # esi = in 8b/-> *(ebp+8) 6/r32/esi # var eax : (address block) = parse-mu-block(in) (parse-mu-block %esi) # out->body = eax 89/<- *(eax+0x10) 0/r32/eax # Function-body $populate-mu-function-body:end: # . restore registers 5e/pop-to-esi 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return # parses a block, assuming that the leading '{' has already been read by the caller # errors considered: # { abc parse-mu-block: # in : (address buffered-file) -> result/eax : (address block) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 51/push-ecx 52/push-edx 53/push-ebx # 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 open-curly-count/ebx : int = 1 bb/copy-to-ebx 1/imm32 { # line loop $parse-mu-block:line-loop: # if (open-curly-count == 0) break 81 7/subop/compare %ebx 0/imm32 0f 84/jump-if-equal break/disp32 # line = read-line-buffered(in) (clear-stream %ecx) (read-line-buffered *(ebp+8) %ecx) # if (line->write == 0) break 81 7/subop/compare *ecx 0/imm32 0f 84/jump-if-equal break/disp32 # word-slice = next-word(line) (next-word %ecx %edx) # if slice-empty?(word-slice) continue (slice-empty? %ecx) 3d/compare-eax-and 0/imm32 75/jump-if-not-equal loop/disp8 # if (slice-starts-with?(word-slice, '#') continue # . eax = *word-slice->start 8b/-> *edx 0/r32/eax 8a/copy-byte *eax 0/r32/AL 81 4/subop/and %eax 0xff/imm32 # . if (eax == '#') continue 3d/compare-eax-and 0x23/imm32/hash 74/jump-if-equal loop/disp8 { # if slice-equal?(word-slice, "{") ++open-curly-count { (slice-equal? %ecx "{") 3d/compare-eax-and 0/imm32 74/jump-if-equal break/disp8 43/increment-ebx eb/jump $curly-found:end/disp8 } # else if slice-equal?(word-slice, "}") --open-curly-count { (slice-equal? %ecx "}") 3d/compare-eax-and 0/imm32 74/jump-if-equal break/disp8 4b/decrement-ebx eb/jump $curly-found:end/disp8 } # else break eb/jump $parse-mu-block:end/disp8 } # - check for invalid tokens after curly $curly-found:end: # second-word-slice = next-word(line) (next-word %ecx %edx) # if slice-empty?(second-word-slice) continue (slice-empty? %ecx) 3d/compare-eax-and 0/imm32 0f 85/jump-if-not-equal loop/disp32 # if (slice-starts-with?(second-word-slice, '#') continue # . eax = *second-word-slice->start 8b/-> *edx 0/r32/eax 8a/copy-byte *eax 0/r32/AL 81 4/subop/and %eax 0xff/imm32 # . if (eax == '#') continue 3d/compare-eax-and 0x23/imm32/hash 0f 84/jump-if-equal loop/disp32 # abort eb/jump $parse-mu-block:abort/disp8 } # end line loop $parse-mu-block:end: # . reclaim locals 81 0/subop/add %esp 0x214/imm32 # . restore registers 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 $parse-mu-block:abort: # error("'{' or '}' should be on its own line, but got '") (write-buffered Stderr "'{' or '}' should be on its own line, but got '") (rewind-stream %ecx) (write-stream 2 %ecx) (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 new-function: # ad: allocation-descriptor, name: string, subx-name: string, inouts: (address list var), outputs: (address list var), body: (address block), next: (address function) -> result/eax: (address function) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # (allocate *(ebp+8) *Function-size) # => eax 8b/-> *(ebp+0xc) 1/r32/ecx 89/<- *eax 1/r32/ecx # Function-name 8b/-> *(ebp+0x10) 1/r32/ecx 89/<- *(eax+4) 1/r32/ecx # Function-subx-name 8b/-> *(ebp+0x14) 1/r32/ecx 89/<- *(eax+8) 1/r32/ecx # Function-inouts 8b/-> *(ebp+0x18) 1/r32/ecx 89/<- *(eax+0xc) 1/r32/ecx # Function-outputs 8b/-> *(ebp+0x1c) 1/r32/ecx 89/<- *(eax+0x10) 1/r32/ecx # Function-body 8b/-> *(ebp+0x20) 1/r32/ecx 89/<- *(eax+0x14) 1/r32/ecx # Function-next $new-function:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return new-var: # ad: allocation-descriptor, name: string, type: int, block: int, stack-offset: int, register: string -> result/eax: (address var) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # (allocate *(ebp+8) *Var-size) # => eax 8b/-> *(ebp+0xc) 1/r32/ecx 89/<- *eax 1/r32/ecx # Var-name 8b/-> *(ebp+0x10) 1/r32/ecx 89/<- *(eax+4) 1/r32/ecx # Var-type 8b/-> *(ebp+0x14) 1/r32/ecx 89/<- *(eax+8) 1/r32/ecx # Var-block 8b/-> *(ebp+0x18) 1/r32/ecx 89/<- *(eax+0xc) 1/r32/ecx # Var-stack-offset 8b/-> *(ebp+0x1c) 1/r32/ecx 89/<- *(eax+0x10) 1/r32/ecx # Var-register $new-var:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return new-block: # ad: allocation-descriptor, data: (address list statement) -> result/eax: (address statement) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # (allocate *(ebp+8) *Stmt-size) # => eax c7 0/subop/copy *eax 0/imm32/tag/block # Stmt-tag 8b/-> *(ebp+0xc) 1/r32/ecx 89/<- *(eax+4) 1/r32/ecx # Block-statements $new-block:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return new-stmt: # ad: allocation-descriptor, operation: string, inouts: (address list var), outputs: (address list var) -> result/eax: (address statement) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # (allocate *(ebp+8) *Stmt-size) # => eax c7 0/subop/copy *eax 1/imm32/tag/regular-stmt # Stmt-tag 8b/-> *(ebp+0xc) 1/r32/ecx 89/<- *(eax+4) 1/r32/ecx # Stmt1-operation 8b/-> *(ebp+0x10) 1/r32/ecx 89/<- *(eax+8) 1/r32/ecx # Stmt1-inouts 8b/-> *(ebp+0x14) 1/r32/ecx 89/<- *(eax+0xc) 1/r32/ecx # Stmt1-outputs $new-stmt:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return new-vardef: # ad: allocation-descriptor, name: string, type: int -> result/eax: (address statement) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # (allocate *(ebp+8) *Stmt-size) # => eax c7 0/subop/copy *eax 2/imm32/tag/var-on-stack # Stmt-tag 8b/-> *(ebp+0xc) 1/r32/ecx 89/<- *(eax+4) 1/r32/ecx # Vardef-name 8b/-> *(ebp+0x10) 1/r32/ecx 89/<- *(eax+8) 1/r32/ecx # Vardef-type $new-vardef:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return new-regvardef: # ad: allocation-descriptor, name: string, type: int, register: string -> result/eax: (address statement) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # (allocate *(ebp+8) *Stmt-size) # => eax c7 0/subop/copy *eax 3/imm32/tag/var-in-register 8b/-> *(ebp+0xc) 1/r32/ecx 89/<- *(eax+4) 1/r32/ecx # Regvardef-name 8b/-> *(ebp+0x10) 1/r32/ecx 89/<- *(eax+8) 1/r32/ecx # Regvardef-type 8b/-> *(ebp+0x14) 1/r32/ecx 89/<- *(eax+0xc) 1/r32/ecx # Regvardef-register $new-regvardef:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return new-named-block: # ad: allocation-descriptor, name: string, data: (address list statement) -> result/eax: (address statement) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # (allocate *(ebp+8) *Stmt-size) # => eax c7 0/subop/copy *eax 4/imm32/tag/named-block 8b/-> *(ebp+0xc) 1/r32/ecx 89/<- *(eax+4) 1/r32/ecx # Named-block-name 8b/-> *(ebp+0x10) 1/r32/ecx 89/<- *(eax+8) 1/r32/ecx # Named-block-statements $new-named-block:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return new-list: # ad: allocation-descriptor, value: _type, next: (address list _type) -> result/eax : (address list _type) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # (allocate *(ebp+8) *List-size) # => eax 8b/-> *(ebp+0xc) 1/r32/ecx 89/<- *eax 1/r32/ecx # List-value 8b/-> *(ebp+0x10) 1/r32/ecx 89/<- *(eax+4) 1/r32/ecx # List-next $new-list:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return append-list: # ad: allocation-descriptor, value: _type, list: (address list _type) -> result/eax : (address list _type) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # (allocate *(ebp+8) *List-size) # => eax 8b/-> *(ebp+0xc) 1/r32/ecx 89/<- *eax 1/r32/ecx # List-value # if (list == null) return result 81 7/subop/compare *(ebp+0x10) 0/imm32 74/jump-if-equal $new-list:end/disp8 # otherwise append # var curr/ecx = list 8b/-> *(ebp+0x10) 1/r32/ecx # while (curr->next != null) curr = curr->next { 81 7/subop/compare *(ecx+4) 0/imm32 # List-next 74/jump-if-equal break/disp8 # curr = curr->next 8b/-> *(ecx+4) 1/r32/ecx eb/jump loop/disp8 } # curr->next = result 89/<- *(ecx+4) 0/r32/eax # return list 8b/-> *(ebp+0x10) 0/r32/eax $append-list:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return ####################################################### # Type-checking ####################################################### check-mu-types: # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # $check-mu-types:end: # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return ####################################################### # Code-generation ####################################################### emit-subx: # out : (address buffered-file) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 51/push-ecx 57/push-edi # edi = out 8b/-> *(ebp+8) 7/r32/edi # var curr/ecx : (address function) = Program 8b/-> *Program 1/r32/ecx { # if (curr == NULL) break 81 7/subop/compare %ecx 0/imm32 0f 84/jump-if-equal break/disp32 (emit-subx-function %edi %ecx) # curr = curr->next 8b/-> *(ecx+0x14) 1/r32/ecx # Function-next e9/jump loop/disp32 } $emit-subx:end: # . restore registers 5f/pop-to-edi 59/pop-to-ecx 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return emit-subx-function: # out : (address buffered-file), f : (address function) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 51/push-ecx 57/push-edi # edi = out 8b/-> *(ebp+8) 7/r32/edi # ecx = f 8b/-> *(ebp+0xc) 1/r32/ecx # (write-buffered %edi *ecx) (write-buffered %edi ":\n") (emit-subx-prologue %edi) (emit-subx-block %edi *(ecx+0x10)) # Function-body (emit-subx-epilogue %edi) $emit-subx-function:end: # . restore registers 5f/pop-to-edi 59/pop-to-ecx 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return emit-subx-block: # out : (address buffered-file), block : (address block) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # $emit-subx-block:end: # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return emit-subx-statement: # out : (address buffered-file), stmt : (address statement), vars : (stack var), primitives : (address primitive), functions : (address function) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 51/push-ecx # if stmt matches a primitive, emit it { $emit-subx-statement:primitive: (find-matching-primitive *(ebp+0x14) *(ebp+0xc)) # primitives, stmt => curr/eax 3d/compare-eax-and 0/imm32 74/jump-if-equal break/disp8 (emit-subx-primitive *(ebp+8) *(ebp+0xc) *(ebp+0x10) %eax) # out, stmt, vars, curr e9/jump $emit-subx-statement:end/disp32 } # else if stmt matches a function, emit a call to it { $emit-subx-statement:call: (find-matching-function *(ebp+0x18) *(ebp+0xc)) # functions, stmt => curr/eax 3d/compare-eax-and 0/imm32 74/jump-if-equal break/disp8 (emit-subx-call *(ebp+8) *(ebp+0xc) *(ebp+0x10) %eax) # out, stmt, vars, curr e9/jump $emit-subx-statement:end/disp32 } # else abort e9/jump $emit-subx-statement:abort/disp32 $emit-subx-statement:end: # . restore registers 59/pop-to-ecx 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return $emit-subx-statement:abort: # error("couldn't translate '" stmt "'\n") (write-buffered Stderr "couldn't translate '") #? (emit-string Stderr *(ebp+0xc)) # TODO (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 # Primitives supported == data Primitives: # increment var => ff 0/subop/increment *(ebp+__) "increment"/imm32/name Single-int-var-on-stack/imm32/inouts 0/imm32/no-outputs "ff 0/subop/increment"/imm32/subx-name 1/imm32/rm32-is-first-inout 0/imm32/no-r32 0/imm32/no-imm32 _Primitive-inc-reg/imm32/next _Primitive-inc-reg: # var/reg <- increment => ff 0/subop/increment %__ "increment"/imm32/name 0/imm32/no-inouts Single-int-var-in-some-register/imm32/outputs "ff 0/subop/increment"/imm32/subx-name 3/imm32/rm32-is-first-output 0/imm32/no-r32 0/imm32/no-imm32 _Primitive-add-reg-to-reg/imm32/next _Primitive-add-reg-to-reg: # var1/reg <- add var2/reg => 01 var1/rm32 var2/r32 "add"/imm32/name Single-int-var-in-some-register/imm32/inouts Single-int-var-in-some-register/imm32/outputs "01"/imm32/subx-name 3/imm32/rm32-is-first-output 1/imm32/r32-is-first-inout 0/imm32/no-imm32 _Primitive-add-reg-to-mem/imm32/next _Primitive-add-reg-to-mem: # add-to var1 var2/reg => 01 var1 var2/r32 "add-to"/imm32/name Int-var-and-second-int-var-in-some-register/imm32/inouts 0/imm32/outputs "01"/imm32/subx-name 1/imm32/rm32-is-first-inout 2/imm32/r32-is-second-inout 0/imm32/no-imm32 _Primitive-add-mem-to-reg/imm32/next _Primitive-add-mem-to-reg: # var1/reg <- add var2 => 03 var2/rm32 var1/r32 "add"/imm32/name Single-int-var-on-stack/imm32/inouts Single-int-var-in-some-register/imm32/outputs "03"/imm32/subx-name 1/imm32/rm32-is-first-inout 3/imm32/r32-is-first-output 0/imm32/no-imm32 _Primitive-add-lit-to-reg/imm32/next _Primitive-add-lit-to-reg: # var1/reg <- add lit => 81 0/subop/add var1/rm32 lit/imm32 "add"/imm32/name Single-lit-var/imm32/inouts Single-int-var-in-some-register/imm32/outputs "81 0/subop/add"/imm32/subx-name 3/imm32/rm32-is-first-output 0/imm32/no-r32 1/imm32/imm32-is-first-inout _Primitive-add-lit-to-mem/imm32/next _Primitive-add-lit-to-mem: # add-to var1, lit => 81 0/subop/add var1/rm32 lit/imm32 "add-to"/imm32/name Int-var-and-literal/imm32/inouts 0/imm32/outputs "81 0/subop/add"/imm32/subx-name 1/imm32/rm32-is-first-inout 0/imm32/no-r32 2/imm32/imm32-is-first-inout 0/imm32/next Single-int-var-on-stack: Int-var-on-stack/imm32 0/imm32/next Int-var-on-stack: "arg1"/imm32/name 1/imm32/type-int 1/imm32/some-block-depth 1/imm32/some-stack-offset 0/imm32/no-register Int-var-and-second-int-var-in-some-register: Int-var-on-stack/imm32 Single-int-var-in-some-register/imm32/next Int-var-and-literal: Int-var-on-stack/imm32 Single-lit-var/imm32/next Single-int-var-in-some-register: Int-var-in-some-register/imm32 0/imm32/next Int-var-in-some-register: "arg1"/imm32/name 1/imm32/type-int 1/imm32/some-block-depth 0/imm32/no-stack-offset "*"/imm32/register Single-lit-var: Lit-var/imm32 0/imm32/next Lit-var: "literal"/imm32/name 0/imm32/type-literal 1/imm32/some-block-depth 0/imm32/no-stack-offset 0/imm32/no-register == code emit-subx-primitive: # out : (address buffered-file), stmt : (address statement), vars : (address variable), primitive : (address function) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 51/push-ecx # ecx = primitive 8b/-> *(ebp+0x14) 1/r32/ecx # emit primitive name (write-buffered *(ebp+8) *(ecx+0xc)) # Primitive-subx-name # emit rm32 if necessary (emit-subx-rm32 *(ebp+8) *(ecx+0x10) *(ebp+0xc)) # out, Primitive-subx-rm32, stmt # emit r32 if necessary (emit-subx-r32 *(ebp+8) *(ecx+0x14) *(ebp+0xc)) # out, Primitive-subx-r32, stmt # emit imm32 if necessary (emit-subx-imm32 *(ebp+8) *(ecx+0x18) *(ebp+0xc)) # out, Primitive-subx-imm32, stmt $emit-subx-primitive:end: # . restore registers 59/pop-to-ecx 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return emit-subx-rm32: # out : (address buffered-file), l : arg-location, stmt : (address statement) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax # if (l == 0) return 81 7/subop/compare *(ebp+0xc) 0/imm32 74/jump-if-equal $emit-subx-rm32:end/disp8 # (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc)) # stmt, l => var/eax (emit-subx-var-as-rm32 *(ebp+8) %eax) # out, var $emit-subx-rm32:end: # . restore registers 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return get-stmt-operand-from-arg-location: # stmt : (address statement), l : arg-location -> var/eax : (address variable) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # eax = l 8b/-> *(ebp+0xc) 0/r32/eax # ecx = stmt 8b/-> *(ebp+8) 1/r32/ecx # if (l == 1) return stmt->inouts->var { 3d/compare-eax-and 1/imm32 75/jump-if-not-equal break/disp8 $get-stmt-operand-from-arg-location:1: 8b/-> *(ecx+8) 0/r32/eax # Stmt1-inouts 8b/-> *eax 0/r32/eax # Operand-var eb/jump $get-stmt-operand-from-arg-location:end/disp8 } # if (l == 2) return stmt->inouts->next->var { 3d/compare-eax-and 2/imm32 75/jump-if-not-equal break/disp8 $get-stmt-operand-from-arg-location:2: 8b/-> *(ecx+8) 0/r32/eax # Stmt1-inouts 8b/-> *(eax+4) 0/r32/eax # Operand-next 8b/-> *eax 0/r32/eax # Operand-var eb/jump $get-stmt-operand-from-arg-location:end/disp8 } # if (l == 3) return stmt->outputs { 3d/compare-eax-and 3/imm32 75/jump-if-not-equal break/disp8 $get-stmt-operand-from-arg-location:3: 8b/-> *(ecx+0xc) 0/r32/eax # Stmt1-outputs 8b/-> *eax 0/r32/eax # Operand-var eb/jump $get-stmt-operand-from-arg-location:end/disp8 } # abort e9/jump $get-stmt-operand-from-arg-location:abort/disp32 $get-stmt-operand-from-arg-location:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return $get-stmt-operand-from-arg-location:abort: # error("invalid arg-location " eax) (write-buffered Stderr "invalid arg-location ") (print-int32-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 emit-subx-r32: # out : (address buffered-file), l : arg-location, stmt : (address statement) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 51/push-ecx # if (location == 0) return 81 7/subop/compare *(ebp+0xc) 0/imm32 0f 84/jump-if-equal $emit-subx-r32:end/disp32 # (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc)) # stmt, l => var/eax (maybe-get Registers *(eax+0x10) 8) # Var-register => eax : (address register-index) (write-buffered *(ebp+8) Space) (print-int32-buffered *(ebp+8) *eax) (write-buffered *(ebp+8) "/r32") $emit-subx-r32:end: # . restore registers 59/pop-to-ecx 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return emit-subx-imm32: # out : (address buffered-file), l : arg-location, stmt : (address statement) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 51/push-ecx # if (location == 0) return 81 7/subop/compare *(ebp+0xc) 0/imm32 74/jump-if-equal $emit-subx-imm32:end/disp8 # (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc)) # stmt, l => var/eax (write-buffered *(ebp+8) Space) (write-buffered *(ebp+8) *eax) # Var-name (write-buffered *(ebp+8) "/imm32") $emit-subx-imm32:end: # . restore registers 59/pop-to-ecx 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return emit-subx-call: # out : (address buffered-file), stmt : (address statement), vars : (address variable), callee : (address function) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax 51/push-ecx # (write-buffered *(ebp+8) "(") # - emit function name 8b/-> *(ebp+0x14) 1/r32/ecx (write-buffered *(ebp+8) *(ecx+4)) # Function-subx-name # - emit arguments # var curr/ecx : (list var) = stmt->inouts 8b/-> *(ebp+0xc) 1/r32/ecx 8b/-> *(ecx+8) 1/r32/ecx # Stmt1-inouts { # if (curr == null) break 81 7/subop/compare %ecx 0/imm32 74/jump-if-equal break/disp8 # (emit-subx-call-operand *(ebp+8) *ecx) # curr = curr->next 8b/-> *(ecx+4) 1/r32/ecx } # (write-buffered *(ebp+8) ")") $emit-subx-call:end: # . restore registers 59/pop-to-ecx 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return emit-subx-call-operand: # out : (address buffered-file), operand : (address variable) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax # eax = operand 8b/-> *(ebp+0xc) 0/r32/eax # if non-literal, emit appropriately (emit-subx-var-as-rm32 *(ebp+8) %eax) # else if (operand->type == literal) emit "__" { 81 7/subop/compare *(eax+4) 0/imm32 # Var-type 75/jump-if-not-equal break/disp8 $emit-subx-call-operand:literal: (write-buffered *(ebp+8) Space) (write-buffered *(ebp+8) *eax) } $emit-subx-call-operand:end: # . restore registers 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return emit-subx-var-as-rm32: # out : (address buffered-file), operand : (address variable) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax # eax = operand 8b/-> *(ebp+0xc) 0/r32/eax # if (operand->register) emit "%__" { 81 7/subop/compare *(eax+0x10) 0/imm32 # Var-register 74/jump-if-equal break/disp8 $emit-subx-var-as-rm32:register: (write-buffered *(ebp+8) " %") (write-buffered *(ebp+8) *(eax+0x10)) # Var-register } # else if (operand->stack-offset) emit "*(ebp+__)" { 81 7/subop/compare *(eax+0xc) 0/imm32 # Var-stack-offset 74/jump-if-equal break/disp8 $emit-subx-var-as-rm32:stack: (write-buffered *(ebp+8) Space) (write-buffered *(ebp+8) "*(ebp+") 8b/-> *(ebp+0xc) 0/r32/eax (print-int32-buffered *(ebp+8) *(eax+0xc)) # Var-stack-offset (write-buffered *(ebp+8) ")") } $emit-subx-var-as-rm32:end: # . restore registers 58/pop-to-eax # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return find-matching-function: # functions : (address function), stmt : (address statement) -> result/eax : (address function) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # var curr/ecx : (address function) = functions 8b/-> *(ebp+8) 1/r32/ecx { # if (curr == null) break 81 7/subop/compare %ecx 0/imm32 74/jump-if-equal break/disp8 # if match(curr, stmt) return curr { (mu-stmt-matches-function? *(ebp+0xc) %ecx) # => eax 3d/compare-eax-and 0/imm32 74/jump-if-equal break/disp8 89/<- %eax 1/r32/ecx eb/jump $find-matching-function:end/disp8 } # curr = curr->next 8b/-> *(ecx+0x10) 1/r32/ecx # Function-next eb/jump loop/disp8 } # return null b8/copy-to-eax 0/imm32 $find-matching-function:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return find-matching-primitive: # primitives : (address primitive), stmt : (address statement) -> result/eax : (address primitive) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # var curr/ecx : (address primitive) = primitives 8b/-> *(ebp+8) 1/r32/ecx { $find-matching-primitive:loop: # if (curr == null) break 81 7/subop/compare %ecx 0/imm32 74/jump-if-equal break/disp8 # if match(curr, stmt) return curr { (mu-stmt-matches-primitive? *(ebp+0xc) %ecx) # => eax 3d/compare-eax-and 0/imm32 74/jump-if-equal break/disp8 89/<- %eax 1/r32/ecx eb/jump $find-matching-function:end/disp8 } $find-matching-primitive:next-primitive: # curr = curr->next 8b/-> *(ecx+0x1c) 1/r32/ecx # Primitive-next eb/jump loop/disp8 } # return null b8/copy-to-eax 0/imm32 $find-matching-primitive:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return mu-stmt-matches-function?: # stmt : (address statement), function : (address opcode-info) => result/eax : boolean # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx # return primitive->name == stmt->operation 8b/-> *(ebp+8) 1/r32/ecx 8b/-> *(ebp+0xc) 0/r32/eax (string-equal? *(ecx+4) *eax) # Stmt1-operation, Primitive-name => eax $mu-stmt-matches-function?:end: # . restore registers 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return mu-stmt-matches-primitive?: # stmt : (address statement), primitive : (address primitive) => result/eax : boolean # A mu stmt matches a primitive if the name matches, all the inout vars # match, and all the output vars match. # Vars match if types match and registers match. # In addition, a stmt output matches a primitive's output if types match # and the primitive has a wildcard register. # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 51/push-ecx 52/push-edx 53/push-ebx 56/push-esi 57/push-edi # ecx = stmt 8b/-> *(ebp+8) 1/r32/ecx # edx = primitive 8b/-> *(ebp+0xc) 2/r32/edx { $mu-stmt-matches-primitive?:check-name: # if (primitive->name != stmt->operation) return false (string-equal? *(ecx+4) *edx) # Stmt1-operation, Primitive-name => eax 3d/compare-eax-and 0/imm32 75/jump-if-not-equal break/disp8 b8/copy-to-eax 0/imm32 e9/jump $mu-stmt-matches-primitive?:end/disp32 } $mu-stmt-matches-primitive?:check-inouts: # for (curr/esi in stmt->inouts, curr2/edi in primitive->inouts) 8b/-> *(ecx+8) 6/r32/esi # Stmt1-inouts 8b/-> *(edx+4) 7/r32/edi # Primitive-inouts { # if (curr == 0) return (curr2 == 0) { 81 7/subop/compare %esi 0/imm32 75/jump-if-not-equal break/disp8 { 81 7/subop/compare %edi 0/imm32 75/jump-if-not-equal break/disp8 # return true b8/copy-to-eax 1/imm32 e9/jump $mu-stmt-matches-primitive?:end/disp32 } # return false b8/copy-to-eax 0/imm32 e9/jump $mu-stmt-matches-primitive?:end/disp32 } # if (curr2 == 0) return false { 81 7/subop/compare %edi 0/imm32 75/jump-if-not-equal break/disp8 b8/copy-to-eax 0/imm32 e9/jump $mu-stmt-matches-primitive?:end/disp32 } # if (curr != curr2) return false { (operand-matches-primitive? *esi *edi) # => eax 3d/compare-eax-and 0/imm32 75/jump-if-not-equal break/disp8 b8/copy-to-eax 0/imm32 e9/jump $mu-stmt-matches-primitive?:end/disp32 } # curr=curr->next 8b/-> *(esi+4) 6/r32/esi # Operand-next # curr2=curr2->next 8b/-> *(edi+4) 7/r32/edi # Operand-next eb/jump loop/disp8 } $mu-stmt-matches-primitive?:check-outputs: # for (curr/esi in stmt->outputs, curr2/edi in primitive->outputs) 8b/-> *(ecx+0xc) 6/r32/esi # Stmt1-outputs 8b/-> *(edx+8) 7/r32/edi # Primitive-outputs { # if (curr == 0) return (curr2 == 0) { 81 7/subop/compare %esi 0/imm32 75/jump-if-not-equal break/disp8 { 81 7/subop/compare %edi 0/imm32 75/jump-if-not-equal break/disp8 # return true b8/copy-to-eax 1/imm32 e9/jump $mu-stmt-matches-primitive?:end/disp32 } # return false b8/copy-to-eax 0/imm32 e9/jump $mu-stmt-matches-primitive?:end/disp32 } # if (curr2 == 0) return false { 81 7/subop/compare %edi 0/imm32 75/jump-if-not-equal break/disp8 b8/copy-to-eax 0/imm32 e9/jump $mu-stmt-matches-primitive?:end/disp32 } # if (curr != curr2) return false { (operand-matches-primitive? *esi *edi) # => eax 3d/compare-eax-and 0/imm32 75/jump-if-not-equal break/disp8 b8/copy-to-eax 0/imm32 e9/jump $mu-stmt-matches-primitive?:end/disp32 } # curr=curr->next 8b/-> *(ecx+4) 1/r32/ecx # Operand-next # curr2=curr2->next 8b/-> *(edx+4) 2/r32/edx # Operand-next eb/jump loop/disp8 } $mu-stmt-matches-primitive?:return-true: b8/copy-to-eax 1/imm32 $mu-stmt-matches-primitive?:end: # . restore registers 5f/pop-to-edi 5e/pop-to-esi 5b/pop-to-ebx 5a/pop-to-edx 59/pop-to-ecx # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return operand-matches-primitive?: # var : (address var), primout-var : (address var) => result/eax : boolean # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 56/push-esi 57/push-edi # esi = var 8b/-> *(ebp+8) 6/r32/esi # edi = primout-var 8b/-> *(ebp+0xc) 7/r32/edi # if (var->type != primout-var->type) return false 8b/-> *(esi+4) 0/r32/eax # Var-type 39/compare *(edi+4) 0/r32/eax # Var-type b8/copy-to-eax 0/imm32/false 75/jump-if-not-equal $operand-matches-primitive?:end/disp8 # return false if var->register doesn't match primout-var->register { # if addresses are equal, don't return here 8b/-> *(esi+0x10) 0/r32/eax 39/compare *(edi+0x10) 0/r32/eax 74/jump-if-equal break/disp8 # if either address is 0, return false 3d/compare-eax-and 0/imm32 74/jump-if-equal $operand-matches-primitive?:end/disp8 # eax goes from meaning var->register to result 81 7/subop/compare *(edi+0x10) 0/imm32 74/jump-if-equal $operand-matches-primitive?:end/disp8 # eax goes from meaning var->register to result # if primout-var->register is "*", return true (string-equal? *(edi+0x10) "*") # Var-register 3d/compare-eax-and 0/imm32 b8/copy-to-eax 1/imm32/true 75/jump-if-not-equal $operand-matches-primitive?:end/disp8 # if string contents don't match, return false (string-equal? *(esi+0x10) *(edi+0x10)) # Var-register Var-register 3d/compare-eax-and 0/imm32 b8/copy-to-eax 0/imm32/false 74/jump-if-equal $operand-matches-primitive?:end/disp8 } # return true b8/copy-to-eax 1/imm32/true $operand-matches-primitive?:end: # . restore registers 5f/pop-to-edi 5e/pop-to-esi # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-emit-subx-statement-primitive: # Primitive operation on a variable on the stack. # increment foo # => # ff 0/subop/increment *(ebp-8) # # There's a variable on the var stack as follows: # name: 'foo' # type: int # stack-offset: -8 # # There's a primitive with this info: # name: 'increment' # inouts: int/mem # value: 'ff 0/subop/increment' # # There's nothing in functions. # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-foo/ecx : var 68/push 0/imm32/no-register 68/push -8/imm32/stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "foo"/imm32 89/<- %ecx 4/r32/esp # vars/edx : (stack 1) 51/push-ecx/var-foo 68/push 1/imm32/data-length 68/push 1/imm32/top 89/<- %edx 4/r32/esp # operand/ebx : (list var) 68/push 0/imm32/next 51/push-ecx/var-foo 89/<- %ebx 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 68/push 0/imm32/outputs 53/push-ebx/operands 68/push "increment"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # primitives/ebx : primitive 68/push 0/imm32/next 68/push 0/imm32/no-imm32 68/push 0/imm32/no-r32 68/push 1/imm32/rm32-is-first-inout 68/push "ff 0/subop/increment"/imm32/subx-name 68/push 0/imm32/outputs 53/push-ebx/inouts # hack; in practice we won't have the same var in function definition and call 68/push "increment"/imm32/name 89/<- %ebx 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 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 "ff 0/subop/increment *(ebp+0xfffffff8)" "F - test-emit-subx-statement-primitive") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-emit-subx-statement-primitive-register: # Primitive operation on a variable in a register. # foo <- increment # => # ff 0/subop/increment %eax # sub-optimal, but should suffice # # There's a variable on the var stack as follows: # name: 'foo' # type: int # register: 'eax' # # There's a primitive with this info: # name: 'increment' # out: int/reg # value: 'ff 0/subop/increment' # # There's nothing in functions. # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-foo/ecx : var in eax 68/push "eax"/imm32/register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "foo"/imm32 89/<- %ecx 4/r32/esp # vars/edx : (stack 1) 51/push-ecx/var-foo 68/push 1/imm32/data-length 68/push 1/imm32/top 89/<- %edx 4/r32/esp # operand/ebx : (list var) 68/push 0/imm32/next 51/push-ecx/var-foo 89/<- %ebx 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 53/push-ebx/outputs 68/push 0/imm32/inouts 68/push "increment"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # formal-var/ebx : var in any register 68/push Any-register/imm32 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "dummy"/imm32 89/<- %ebx 4/r32/esp # operand/ebx : (list var) 68/push 0/imm32/next 53/push-ebx/formal-var 89/<- %ebx 4/r32/esp # primitives/ebx : primitive 68/push 0/imm32/next 68/push 0/imm32/no-imm32 68/push 0/imm32/no-r32 68/push 3/imm32/rm32-in-first-output 68/push "ff 0/subop/increment"/imm32/subx-name 53/push-ebx/outputs 68/push 0/imm32/inouts 68/push "increment"/imm32/name 89/<- %ebx 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 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 "ff 0/subop/increment %eax" "F - test-emit-subx-statement-primitive-register") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-emit-subx-statement-select-primitive: # Select the right primitive between overloads. # foo <- increment # => # ff 0/subop/increment %eax # sub-optimal, but should suffice # # There's a variable on the var stack as follows: # name: 'foo' # type: int # register: 'eax' # # There's two primitives, as follows: # - name: 'increment' # out: int/reg # value: 'ff 0/subop/increment' # - name: 'increment' # inout: int/mem # value: 'ff 0/subop/increment' # # There's nothing in functions. # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-foo/ecx : var in eax 68/push "eax"/imm32/register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "foo"/imm32 89/<- %ecx 4/r32/esp # vars/edx : (stack 1) 51/push-ecx/var-foo 68/push 1/imm32/data-length 68/push 1/imm32/top 89/<- %edx 4/r32/esp # real-outputs/edi : (list var) 68/push 0/imm32/next 51/push-ecx/var-foo 89/<- %edi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 57/push-edi/outputs 68/push 0/imm32/inouts 68/push "increment"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # formal-var/ebx : var in any register 68/push Any-register/imm32 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "dummy"/imm32 89/<- %ebx 4/r32/esp # formal-outputs/ebx : (list var) 68/push 0/imm32/next 53/push-ebx/formal-var 89/<- %ebx 4/r32/esp # primitive1/ebx : primitive 68/push 0/imm32/next 68/push 0/imm32/no-imm32 68/push 0/imm32/no-r32 68/push 3/imm32/rm32-in-first-output 68/push "ff 0/subop/increment"/imm32/subx-name 53/push-ebx/outputs/formal-outputs 68/push 0/imm32/inouts 68/push "increment"/imm32/name 89/<- %ebx 4/r32/esp # primitives/ebx : primitive 53/push-ebx/next 68/push 0/imm32/no-imm32 68/push 0/imm32/no-r32 68/push 1/imm32/rm32-is-first-inout 68/push "ff 0/subop/increment"/imm32/subx-name 68/push 0/imm32/outputs 57/push-edi/inouts/real-outputs # hack; in practice we won't have the same var in function definition and call 68/push "increment"/imm32/name 89/<- %ebx 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 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 "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-emit-subx-statement-select-primitive-2: # Select the right primitive between overloads. # foo <- increment # => # ff 0/subop/increment %eax # sub-optimal, but should suffice # # There's a variable on the var stack as follows: # name: 'foo' # type: int # register: 'eax' # # There's two primitives, as follows: # - name: 'increment' # out: int/reg # value: 'ff 0/subop/increment' # - name: 'increment' # inout: int/mem # value: 'ff 0/subop/increment' # # There's nothing in functions. # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-foo/ecx : var in eax 68/push "eax"/imm32/register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "foo"/imm32 89/<- %ecx 4/r32/esp # vars/edx : (stack 1) 51/push-ecx/var-foo 68/push 1/imm32/data-length 68/push 1/imm32/top 89/<- %edx 4/r32/esp # inouts/edi : (list var) 68/push 0/imm32/next 51/push-ecx/var-foo 89/<- %edi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 68/push 0/imm32/outputs 57/push-edi/inouts 68/push "increment"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # formal-var/ebx : var in any register 68/push Any-register/imm32 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "dummy"/imm32 89/<- %ebx 4/r32/esp # operand/ebx : (list var) 68/push 0/imm32/next 53/push-ebx/formal-var 89/<- %ebx 4/r32/esp # primitive1/ebx : primitive 68/push 0/imm32/next 68/push 0/imm32/no-imm32 68/push 0/imm32/no-r32 68/push 3/imm32/rm32-in-first-output 68/push "ff 0/subop/increment"/imm32/subx-name 53/push-ebx/outputs/formal-outputs 68/push 0/imm32/inouts 68/push "increment"/imm32/name 89/<- %ebx 4/r32/esp # primitives/ebx : primitive 53/push-ebx/next 68/push 0/imm32/no-imm32 68/push 0/imm32/no-r32 68/push 1/imm32/rm32-is-first-inout 68/push "ff 0/subop/increment"/imm32/subx-name 68/push 0/imm32/outputs 57/push-edi/inouts/real-outputs # hack; in practice we won't have the same var in function definition and call 68/push "increment"/imm32/name 89/<- %ebx 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi %edx %ebx 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 "ff 0/subop/increment %eax" "F - test-emit-subx-statement-select-primitive-2") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-increment-register: # Select the right primitive between overloads. # foo <- increment # => # ff 0/subop/increment %eax # sub-optimal, but should suffice # # There's a variable on the var stack as follows: # name: 'foo' # type: int # register: 'eax' # # Primitives are the global definitions. # # There are no functions defined. # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-foo/ecx : var in eax 68/push "eax"/imm32/register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "foo"/imm32 89/<- %ecx 4/r32/esp # vars/edx : (stack 1) 51/push-ecx/var-foo 68/push 1/imm32/data-length 68/push 1/imm32/top 89/<- %edx 4/r32/esp # real-outputs/edi : (list var) 68/push 0/imm32/next 51/push-ecx/var-foo 89/<- %edi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 57/push-edi/outputs 68/push 0/imm32/inouts 68/push "increment"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 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 "ff 0/subop/increment %eax" "F - test-increment-register") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-increment-var: # Select the right primitive between overloads. # foo <- increment # => # ff 0/subop/increment %eax # sub-optimal, but should suffice # # There's a variable on the var stack as follows: # name: 'foo' # type: int # register: 'eax' # # Primitives are the global definitions. # # There are no functions defined. # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-foo/ecx : var in eax 68/push "eax"/imm32/register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "foo"/imm32 89/<- %ecx 4/r32/esp # vars/edx : (stack 1) 51/push-ecx/var-foo 68/push 1/imm32/data-length 68/push 1/imm32/top 89/<- %edx 4/r32/esp # inouts/edi : (list var) 68/push 0/imm32/next 51/push-ecx/var-foo 89/<- %edi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 68/push 0/imm32/outputs 57/push-edi/inouts 68/push "increment"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi %edx Primitives 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 "ff 0/subop/increment %eax" "F - test-increment-var") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-add-reg-to-reg: # var1/reg <- add var2/reg # => # 01 %var1 var2 # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-var1/ecx : var in eax 68/push "eax"/imm32/register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "var1"/imm32 89/<- %ecx 4/r32/esp # var-var2/edx : var in ecx 68/push "ecx"/imm32/register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "var2"/imm32 89/<- %edx 4/r32/esp # inouts/esi : (list var2) 68/push 0/imm32/next 52/push-edx/var-var2 89/<- %esi 4/r32/esp # outputs/edi : (list var1) 68/push 0/imm32/next 51/push-ecx/var-var1 89/<- %edi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 57/push-edi/outputs 56/push-esi/inouts 68/push "add"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 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 "01 %eax 0x00000001/r32" "F - test-add-reg-to-reg") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-add-reg-to-mem: # add-to var1 var2/reg # => # 01 *(ebp+__) var2 # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-var1/ecx : var 68/push 0/imm32/no-register 68/push 8/imm32/stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "var1"/imm32 89/<- %ecx 4/r32/esp # var-var2/edx : var in ecx 68/push "ecx"/imm32/register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "var2"/imm32 89/<- %edx 4/r32/esp # inouts/esi : (list var2) 68/push 0/imm32/next 52/push-edx/var-var2 89/<- %esi 4/r32/esp # inouts = (list var1 var2) 56/push-esi/next 51/push-ecx/var-var1 89/<- %esi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 68/push 0/imm32/outputs 56/push-esi/inouts 68/push "add-to"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 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 "01 *(ebp+0x00000008) 0x00000001/r32" "F - test-add-reg-to-mem") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-add-mem-to-reg: # var1/reg <- add var2 # => # 03 *(ebp+__) var1 # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-var1/ecx : var in eax 68/push "eax"/imm32/register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "var1"/imm32 89/<- %ecx 4/r32/esp # var-var2/edx : var 68/push 0/imm32/no-register 68/push 8/imm32/stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "var2"/imm32 89/<- %edx 4/r32/esp # inouts/esi : (list var2) 68/push 0/imm32/next 52/push-edx/var-var2 89/<- %esi 4/r32/esp # outputs/edi : (list var1) 68/push 0/imm32/next 51/push-ecx/var-var1 89/<- %edi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 57/push-edi/outputs 56/push-esi/inouts 68/push "add"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 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 "03 *(ebp+0x00000008) 0x00000000/r32" "F - test-add-mem-to-reg") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-add-literal-to-reg: # var1/eax <- add 0x34 # => # 81 0/subop/add %eax 0x34/imm32 # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-var1/ecx : var in eax 68/push "eax"/imm32/register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "var1"/imm32 89/<- %ecx 4/r32/esp # var-var2/edx : var literal 68/push 0/imm32/no-register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 0/imm32/type-literal 68/push "0x34"/imm32 89/<- %edx 4/r32/esp # inouts/esi : (list var2) 68/push 0/imm32/next 52/push-edx/var-var2 89/<- %esi 4/r32/esp # outputs/edi : (list var1) 68/push 0/imm32/next 51/push-ecx/var-var1 89/<- %edi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 57/push-edi/outputs 56/push-esi/inouts 68/push "add"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 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 "81 0/subop/add %eax 0x34/imm32" "F - test-add-literal-to-reg") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-add-literal-to-mem: # add-to var1, 0x34 # => # 81 0/subop/add %eax 0x34/imm32 # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-var1/ecx : var 68/push 0/imm32/no-register 68/push 8/imm32/stack-offset 68/push 1/imm32/block-depth 68/push 1/imm32/type-int 68/push "var1"/imm32 89/<- %ecx 4/r32/esp # var-var2/edx : var literal 68/push 0/imm32/no-register 68/push 0/imm32/no-stack-offset 68/push 1/imm32/block-depth 68/push 0/imm32/type-literal 68/push "0x34"/imm32 89/<- %edx 4/r32/esp # inouts/esi : (list var2) 68/push 0/imm32/next 52/push-edx/var-var2 89/<- %esi 4/r32/esp # inouts = (list var1 inouts) 56/push-esi/next 51/push-ecx/var-var1 89/<- %esi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 68/push 0/imm32/outputs 56/push-esi/inouts 68/push "add-to"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi 0 Primitives 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 "81 0/subop/add *(ebp+0x00000008) 0x34/imm32" "F - test-add-literal-to-mem") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-emit-subx-statement-function-call: # Call a function on a variable on the stack. # f foo # => # (f2 *(ebp-8)) # (Changing the function name supports overloading in general, but here it # just serves to help disambiguate things.) # # There's a variable on the var stack as follows: # name: 'foo' # type: int # stack-offset: -8 # # There's nothing in primitives. # # There's a function with this info: # name: 'f' # inout: int/mem # value: 'f2' # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-foo/ecx : var 68/push 0/imm32/no-register 68/push -8/imm32/stack-offset 68/push 0/imm32/block-depth 68/push 1/imm32/type-int 68/push "foo"/imm32 89/<- %ecx 4/r32/esp # vars/edx = (stack 1) 51/push-ecx/var-foo 68/push 1/imm32/data-length 68/push 1/imm32/top 89/<- %edx 4/r32/esp # operands/esi : (list var) 68/push 0/imm32/next 51/push-ecx/var-foo 89/<- %esi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 68/push 0/imm32/outputs 56/push-esi/inouts 68/push "f"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # functions/ebx : function 68/push 0/imm32/next 68/push 0/imm32/body 68/push 0/imm32/outputs 51/push-ecx/inouts # hack; in practice we won't have the same var in function definition and call 68/push "f2"/imm32/subx-name 68/push "f"/imm32/name 89/<- %ebx 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx) (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 "(f2 *(ebp+0xfffffff8))" "F - test-emit-subx-statement-function-call") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-emit-subx-statement-function-call-with-literal-arg: # Call a function on a literal. # f 34 # => # (f2 34) # # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # setup (clear-stream _test-output-stream) (clear-stream _test-output-buffered-file->buffer) # var-foo/ecx : literal 68/push 0/imm32/no-register 68/push 0/imm32/no-stack-offset 68/push 0/imm32/block-depth 68/push 0/imm32/type-literal 68/push "34"/imm32 89/<- %ecx 4/r32/esp # vars/edx = (stack 1) 51/push-ecx/var-foo 68/push 1/imm32/data-length 68/push 1/imm32/top 89/<- %edx 4/r32/esp # operands/esi : (list var) 68/push 0/imm32/next 51/push-ecx/var-foo 89/<- %esi 4/r32/esp # stmt/esi : statement 68/push 0/imm32/next 68/push 0/imm32/outputs 56/push-esi/inouts 68/push "f"/imm32/operation 68/push 1/imm32 89/<- %esi 4/r32/esp # functions/ebx : function 68/push 0/imm32/next 68/push 0/imm32/body 68/push 0/imm32/outputs 51/push-ecx/inouts # hack; in practice we won't have the same var in function definition and call 68/push "f2"/imm32/subx-name 68/push "f"/imm32/name 89/<- %ebx 4/r32/esp # convert (emit-subx-statement _test-output-buffered-file %esi %edx 0 %ebx) (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 "(f2 34)" "F - test-emit-subx-statement-function-call-with-literal-arg") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return emit-subx-prologue: # out : (address buffered-file) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (write-buffered *(ebp+8) "# . prologue\n") (write-buffered *(ebp+8) "55/push-ebp\n") (write-buffered *(ebp+8) "89/<- %ebp 4/r32/esp\n") $emit-subx-prologue:end: # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return emit-subx-epilogue: # out : (address buffered-file) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # (write-buffered *(ebp+8) "# . epilogue\n") (write-buffered *(ebp+8) "89/<- %esp 5/r32/ebp\n") (write-buffered *(ebp+8) "5d/pop-to-ebp\n") (write-buffered *(ebp+8) "c3/return\n") $emit-subx-epilogue:end: # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return