# The Mu computer's level-2 language, also called Mu. # http://akkartik.name/post/mu-2019-2 # # To run: # $ ./translate_subx init.linux [0-9]*.subx mu.subx # $ ./a.elf < prog.mu > prog.elf # == 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. The translator should be # implementable in machine code. # - minimize impedance mismatch between source language and SubX target # (e.g. programmer manages registers manually) # - checks over syntax # (e.g. programmer's register allocation is checked) # - runtime checks to avoid complex static analysis # (e.g. array indexing always checks bounds) # == Language description # A program is a sequence of function and type definitions. # # Function example: # fn foo n: int -> _/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. # # Function inputs are always passed in memory (on the stack), while outputs # are always returned in registers. Outputs can't be named; they use the # dummy value '_'. # # 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. # The type of a local variable is either word-length (4 bytes) or starts with 'ref'. # # - 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 # union types # # Formal types: # A program is a linked list of functions # A function contains: # name: (handle array byte) # inouts: linked list of vars <-- 'inouts' is more precise than 'inputs' # data: (handle var) # next: (handle list) # outputs: linked list of vars # data: (handle var) # next: (handle list) # body: (handle block) # A var-type contains: # name: (handle array byte) # type: (handle type-tree) # # A statement can be: # tag 0: a block # tag 1: a simple statement (stmt1) # tag 2: a variable defined on the stack # tag 3: a variable defined in a register # # A block contains: # tag: 0 # statements: (handle list stmt) # name: (handle array byte) -- starting with '$' # # A regular statement contains: # tag: 1 # operation: (handle array byte) # inouts: (handle list operand) # outputs: (handle list var) # # A variable defined on the stack contains: # tag: 2 # name: (handle array byte) # type: (handle type-tree) # # A variable defined in a register contains: # tag: 3 # name: (handle array byte) # type: (handle type-tree) # reg: (handle array byte) # == 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: (handle array byte) # type: (handle type-tree) # block: int # stack-offset: int (added to ebp) # register: (handle array byte) # 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, ... # => # (name inout1 inout2 inout3) # # Formal types: # functions: linked list of info # name: (handle array byte) # inouts: linked list of vars # outputs: linked list of vars # body: block (linked list of statements) # == 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: (handle array byte) # mu-inouts: linked list of vars to check # mu-outputs: linked list of vars to check; at most a singleton # subx-name: (handle array byte) # subx-rm32: enum arg-location # subx-r32: enum arg-location # subx-imm32: enum arg-location # subx-imm8: enum arg-location # subx-disp32: enum arg-location # subx-xm32: enum arg-location # subx-x32: 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: _Program-functions: # (handle function) 0/imm32 _Program-functions->payload: 0/imm32 _Program-types: # (handle typeinfo) 0/imm32 _Program-types->payload: 0/imm32 _Program-signatures: # (handle function) 0/imm32 _Program-signatures->payload: 0/imm32 # Some constants for simulating the data structures described above. # Many constants here come with a type in a comment. # # Sometimes the type is of the value at that offset for the given type. For # example, if you start at a function record and move forward Function-inouts # bytes, you'll find a (handle list var). # # At other times, the type is of the constant itself. For example, the type of # the constant Function-size is (addr int). To get the size of a function, # look in *Function-size. Function-name: # (handle array byte) 0/imm32 Function-inouts: # (handle list var) 8/imm32 Function-outputs: # (handle list var) 0x10/imm32 Function-body: # (handle block) 0x18/imm32 Function-next: # (handle function) 0x20/imm32 Function-size: # (addr int) 0x28/imm32/40 Primitive-name: # (handle array byte) 0/imm32 Primitive-inouts: # (handle list var) 8/imm32 Primitive-outputs: # (handle list var) 0x10/imm32 Primitive-subx-name: # (handle array byte) 0x18/imm32 Primitive-subx-rm32: # enum arg-location 0x20/imm32 Primitive-subx-r32: # enum arg-location 0x24/imm32 Primitive-subx-imm32: # enum arg-location 0x28/imm32 Primitive-subx-imm8: # enum arg-location -- only for bit shifts 0x2c/imm32 Primitive-subx-disp32: # enum arg-location -- only for branches 0x30/imm32 Primitive-subx-xm32: # enum arg-location 0x34/imm32 Primitive-subx-x32: # enum arg-location 0x38/imm32 Primitive-next: # (handle function) 0x3c/imm32 Primitive-size: # (addr int) 0x44/imm32/68 Stmt-tag: # int 0/imm32 Block-stmts: # (handle list stmt) 4/imm32 Block-var: # (handle var) 0xc/imm32 Stmt1-operation: # (handle array byte) 4/imm32 Stmt1-inouts: # (handle stmt-var) 0xc/imm32 Stmt1-outputs: # (handle stmt-var) 0x14/imm32 Vardef-var: # (handle var) 4/imm32 Regvardef-operation: # (handle array byte) 4/imm32 Regvardef-inouts: # (handle stmt-var) 0xc/imm32 Regvardef-outputs: # (handle stmt-var) # will have exactly one element 0x14/imm32 Stmt-size: # (addr int) 0x1c/imm32 Var-name: # (handle array byte) 0/imm32 Var-type: # (handle type-tree) 8/imm32 Var-block-depth: # int -- not available until code-generation time 0x10/imm32 Var-offset: # int -- not available until code-generation time 0x14/imm32 Var-register: # (handle array byte) -- name of a register 0x18/imm32 Var-size: # (addr int) 0x20/imm32 List-value: # (handle _) 0/imm32 List-next: # (handle list _) 8/imm32 List-size: # (addr int) 0x10/imm32 # A stmt-var is like a list of vars with call-site specific metadata Stmt-var-value: # (handle var) 0/imm32 Stmt-var-next: # (handle stmt-var) 8/imm32 Stmt-var-is-deref: # boolean 0x10/imm32 Stmt-var-size: # (addr int) 0x14/imm32 # A live-var is a var augmented with information needed for tracking live # variables. Live-var-value: # (handle var) 0/imm32 Live-var-register-spilled: # boolean; only used if value is in a register, and only during code-gen 8/imm32 Live-var-size: # (addr int) 0xc/imm32 # Types are expressed as trees (s-expressions) of type-ids (ints). Type-tree-is-atom: # boolean 0/imm32 # if is-atom? Type-tree-value: # type-id 4/imm32 Type-tree-value-size: # int (for static data structure sizes) 8/imm32 Type-tree-parameter-name: # (handle array byte) for type parameters 8/imm32 # unless is-atom? Type-tree-left: # (addr type-tree) 4/imm32 Type-tree-right: # (addr type-tree) 0xc/imm32 # Type-tree-size: # (addr int) 0x14/imm32 # Types # TODO: Turn this data structure into valid Mu, with (fake) handles rather than addrs. Type-id: # (stream (addr array byte)) 0/imm32/write # initialized later from Primitive-type-ids 0/imm32/read 0x100/imm32/size # data 0/imm32 # 0 reserved for literals; value is just the name # Not to be used directly, so we don't include a name here. "int"/imm32 # 1 "addr"/imm32 # 2 "array"/imm32 # 3 "handle"/imm32 # 4 "boolean"/imm32 # 5 0/imm32 # 6 reserved for constants; they're like literals, but value is an int in Var-offset # Not to be used directly, so we don't include a name here. "offset"/imm32 # 7: (offset T) is guaranteed to be a 32-bit multiple of size-of(T) # 0x20 "byte"/imm32 # 8 0/imm32 # 9 reserved for array-capacity; value is in Type-tree-size. # Not to be used directly, so we don't include a name here. 0/imm32 # 10 reserved for type parameters; value is (address array byte) in Type-tree-value2. # Not to be used directly, so we don't include a name here. "stream"/imm32 # 11 "slice"/imm32 # 12 "code-point"/imm32 # 13; smallest scannable unit from a text stream "grapheme"/imm32 # 14; smallest printable unit; will eventually be composed of multiple code-points, but currently corresponds 1:1 # only 4-byte graphemes in utf-8 are currently supported; # unclear how we should deal with larger clusters. "float"/imm32 # 15 # 0x40 0/imm32 # 16 reserved for literal strings; value is just the name # Not to be used directly, so we don't include a name here. # TODO: move this up next to literal ints # Keep Primitive-type-ids in sync if you add types here. 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 Primitive-type-ids: # (addr int) 0x44 # == Type definitions # Program->types contains some typeinfo for each type definition. # Types contain vars with types, but can't specify registers. Typeinfo-id: # type-id 0/imm32 Typeinfo-fields: # (handle table (handle array byte) (handle typeinfo-entry)) 4/imm32 # Total size must be >= 0 # During parsing it may take on two additional values: # -2: not yet initialized # -1: in process of being computed # See populate-mu-type-sizes for details. Typeinfo-total-size-in-bytes: # int 0xc/imm32 Typeinfo-next: # (handle typeinfo) 0x10/imm32 Typeinfo-size: # (addr int) 0x18/imm32 # Each entry in the typeinfo->fields table has a pointer to a string and a # pointer to a typeinfo-entry. Typeinfo-fields-row-size: # (addr int) 0x10/imm32 # typeinfo-entry objects have information about a field in a single record type # # each field of a type is represented using two var's: # 1. the input var: expected type of the field; convenient for creating using parse-var-with-type # 2. the output var: a constant containing the byte offset; convenient for code-generation # computing the output happens after parsing; in the meantime we preserve the # order of fields in the 'index' field. Typeinfo-entry-input-var: # (handle var) 0/imm32 Typeinfo-entry-index: # int 8/imm32 Typeinfo-entry-output-var: # (handle var) 0xc/imm32 Typeinfo-entry-size: # (addr int) 0x14/imm32 == code Entry: # . prologue 89/<- %ebp 4/r32/esp (new-segment *Heap-size Heap) #? (test-address-with-right-type-for-stream) # if (argv[1] == "test') run-tests() { # if (argc <= 1) break 81 7/subop/compare *ebp 1/imm32 7e/jump-if-<= break/disp8 # if (argv[1] != "test") break (kernel-string-equal? *(ebp+8) "test") # => eax 3d/compare-eax-and 0/imm32/false 74/jump-if-= 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 (write-buffered Stdout "== code\n") (convert-mu Stdin Stdout Stderr 0) (flush Stdout) # syscall(exit, 0) bb/copy-to-ebx 0/imm32 $mu-main:end: e8/call syscall_exit/disp32 convert-mu: # in: (addr buffered-file), out: (addr buffered-file), err: (addr buffered-file), ed: (addr exit-descriptor) # . prologue 55/push-ebp 89/<- %ebp 4/r32/esp # . save registers 50/push-eax # initialize global data structures c7 0/subop/copy *Next-block-index 1/imm32 8b/-> *Primitive-type-ids 0/r32/eax 89/<- *Type-id 0/r32/eax # stream-write c7 0/subop/copy *_Program-functions 0/imm32 c7 0/subop/copy *_Program-functions->payload 0/imm32 c7 0/subop/copy *_Program-types 0/imm32 c7 0/subop/copy *_Program-types->payload 0/imm32 c7 0/subop/copy *_Program-signatures 0/imm32 c7 0/subop/copy *_Program-signatures->payload 0/imm32 # (parse-mu *(ebp+8) *(ebp+0x10) *(ebp+0x14)) (populate-mu-type-sizes *(ebp+0x10) *(ebp+0x14)) #? (dump-typeinfos "=== typeinfos\n") (check-mu-types *(ebp+0x10) *(ebp+0x14)) (emit-subx *(ebp+0xc) *(ebp+0x10) *(ebp+0x14)) $convert-mu:end: # . restore registers 58/pop-to-eax # . 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 Stderr 0) (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: # . 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 Stderr 0) (flush _test-output-buffered-file) #? # dump _test-output-stream {{{ #? (write 2 "^") #? (write-stream 2 _test-output-stream) #? (write 2 "$\n") #? (rewind-stream _test-output-stream) #? # }}} # check output (check-next-stream-line-equal _test-output-stream "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: # . 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 Stderr 0) (flush _test-output-buffered-file) #? # dump _test-output-stream {{{ #? (write 2 "^") #? (write-stream 2 _test-output-stream) #? (write 2 "$\n") #? (rewind-stream _test-output-stream) #? # }}} # check 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: # . 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 Stderr 0) (flush _test-output-buffered-file) #? # dump _test-output-stream {{{ #? (write 2 "^") #? (write-stream 2 _test-output-stream) #? (write 2 "$\n") #? (rewind-stream _test-output-stream) #? # }}} # check output (check-next-stream-line-equal _test-output-stream "foo:" "F - test-convert-function-with-arg/0") (check-next-stream-line-equal _test-output-stream " # . prologue" "F - test-convert-function-with-arg/1") (check-next-stream-line-equal _test-output-stream " 55/push-ebp" "F - test-convert-function-with-arg/2") (check-next-stream-line-equal _test-output-stream " 89/<- %ebp 4/r32/esp" "F - test-convert-function-with-arg/3") (check-next-stream-line-equal _test-output-stream " # . epilogue" "F - test-convert-function-with-arg/4") (check-next-stream-line-equal _test-output-stream " 89/<- %esp 5/r32/ebp" "F - test-convert-function-with-arg/5") (check-next-stream-line-equal _test-output-stream " 5d/pop-to-ebp" "F - test-convert-function-with-arg/6") (check-next-stream-line-equal _test-output-stream " c3/return" "F - test-convert-function-with-arg/7") # . epilogue 89/<- %esp 5/r32/ebp 5d/pop-to-ebp c3/return test-function-with-redefined-name: # . 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) (clear-stream _test-error-stream) (clear-stream $_test-error-buffered-file->buffer) # var ed/edx: exit-descriptor = tailor-exit-descriptor(16) 68/push 0/imm32 68/push 0/imm32 89/<- %edx 4/r32/esp (tailor-exit-descriptor %edx 0x10) # (write _test-input-stream "fn foo {\n") (write _test-input-stream "}\n") (write _test-input-stream "fn foo {\n") (write _test-input-stream "}\n") # convert (convert-mu _test-input-buffered-file _test-output-buffered-file _test-error-buffered-file %edx) # registers except esp clobbered at this point # restore ed 89/<- %edx 4/r32/esp (flush _test-output-buffered-file) (flush _test-error-buffered-file) #? # dump _test-error-stream {{{ #? (write 2 "^") #? (write-stream 2 _test-error-stream) #? (write 2 "$\n") #? (rewind-stream _test-error-stream) #? # }}} # check output (check-stream-equal _test-output-stream "" "F - test-function-with-redefined-name: output should be empty") (check-next-stream-line-equal _test-error-stream "fn foo defined more than once" "F - test-function-with-redefined-name: error message") # check that stop(1) was called (check-ints-equal *(edx+4) 2 "F - test-function-with-redefined-name: exit status") # don't restore from ebp 81 0/subop/add %esp 8/imm32 # . epilogue 5d/pop-to-ebp c3/return test-function-with-redefined-name-2: # . 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) (clear-stream _test-error-stream) (clear-stream $_test-error-buffered-file->buffer) # var ed/edx: exit-descriptor = tailor-exit-descriptor(16) 68/push 0/imm32 68/push 0/imm32 89/<- %edx 4/r32/esp (tailor-exit-descriptor %edx 0x10) # (write _test-input-stream "fn foo {\n") (write _test-input-stream "}\n") (write _test-input-stream "sig foo\n") # convert (convert-mu _test-input-buffered-file _test-output-buffered-file _test-error-buffered-file %edx) # registers except esp clobbered at this point # restore ed 89/<- %edx 4/r32/esp (flush _test-output-buffered-file) (flush _test-error-buffered-file) #? # dump _test-error-stream {{{ #? (write 2 "^") #? (write-stream 2 _test-error-stream) #? (write 2 "$\n") #? (rewind-stream _test-error-stream) #? # }}} # check output (check-stream-equal _test-output-stream "" "F - test-function-with-redefined-name-2: output should be empty") (check-next-stream-line-equal _test-error-stream "fn foo defined more than once" "F - test-function-with-redefined-name-2: error message") # check that stop(1) was called (check-ints-equal *(edx+4) 2 "F - test-function-with-redefined-name-2: exit status") # don't restore from ebp 81 0/subop/add %esp 8/imm32 # . epilogue 5d/pop-to-ebp c3/return test-function-with-redefined-name-3: # . 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) (clear-stream _test-error-stream) (clear-stream $_test-error-buffered-file->buffer) # var ed/edx: exit-descriptor = tailor-exit-descriptor(16) 68/push 0/imm32 68/push 0/imm32 89/<- %edx 4/r32/esp (tailor-exit-descriptor %edx 0x10) # (write _test-input-stream "sig foo\n") (write _test-input-stream "fn foo {\n") (write _test-input-stream "}\n") # convert (convert-mu _test-input-buffered-file _test-output-buffered-file _test-error-buffered-file %edx) # registers except esp clobbered at this point # restore ed 89/<- %edx 4/r32/esp (flush _test-output-buffered-file) (flush _test-error-buffered-file) #? # dump _test-error-stream {{{ #? (write 2 "^") #? (write-stream 2 _test-error-stream) #? (write 2 "$\n") #? (rewind-stream _test-error-stream) #? # }}} # check output (check-stream-equal _test-output-stream "" "F - test-function-with-redefined-name-3: output should be empty") (check-next-stream-line-equal _test-error-stream "fn foo defined more than once" "F - test-function-with-redefined-name-3: error message") # check that stop(1) was called (check-ints-equal *(edx+4) 2 "F - test-function-with-redefined-name-3: exit status") # don't restore from ebp 81 0/subop/add %esp 8/imm32 # . epilogue 5d/pop-to-ebp c3/return test-function-with-inout-in-register: # . 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) (clear-stream _test-error-stream) (clear-stream $_test-error-buffered-file->buffer) # var ed/edx: exit-descriptor = tailor-exit-descriptor(16) 68/push 0/imm32 68/push 0/imm32 89/<- %edx 4/r32/esp (tailor-exit-descriptor %edx 0x10) # (write _test-input-stream "fn foo x/eax: int {\n") (write _test-input-stream "}\n") # convert (convert-mu _test-input-buffered-file _test-output-buffered-file _test-error-buffered-file %edx) # registers except esp clobbered at this point # restore ed 89/<- %edx 4/r32/esp (flush _test-output-buffered-file) (flush _test-error-buffered-file) #? # dump _test-error-stream {{{ #? (write 2 "^") #? (write-stream 2 _test-error-stream) #? (write 2 "$\n") #? (rewind-stream _test-error-stream) #? # }}} # check output (check-stream-equal _test-output-stream "" "F - test-function-with-inout-in-register: output should be empty") (check-next-stream-line-equal _test-error-stream "fn foo: function inout 'x' cannot be in a register" "F - test-function-with-inout-in-register: error message") # check that stop(1) was called (check-ints-equal *(edx+4) 2 "F - test-function-with-inout-in-register: exit status") # don't restore from ebp 81 0/subop/add %esp 8/imm32 # . epilogue 5d/pop-to-ebp c3/return test-function-with-addr-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) (clear-stream _test-error-stream) (clear-stream $_test-error-buffered-file->buffer) # var ed/edx: exit-descriptor = tailor-exit-descriptor(16) 68/push 0/imm32 68/push 0/imm32 89/<- %edx 4/r32/esp (tailor-exit-descriptor %edx 0x10) # (write _test-input-stream "fn foo -> _/eax: (addr int) {\n") (write _test-input-stream "}\n") # convert (convert-mu _test-input-buffered-file _test-output-buffered-file _test-error-buffered-file %edx) # registers except esp clobbered at this point # restore ed 89/<- %edx 4/r32/esp (flush _test-output-buffered-file) (flush _test-error-buffered-file) #? # dump _test-error-stream {{{ #? (write 2 "^") #? (write-stream 2 _test-error-stream) #? (write 2 "$\n") #? (rewind-stream _test-error-stream) #? # }}} # check output (check-stream-equal _test-output-stream "" "F - test-function-with-addr-output: output should be empty") (check-next-stream-line-equal _test-error-stream "fn foo: output cannot have an addr type; that could allow unsafe addresses to escape the function" "F - test-function-with-addr-output: error message") # check that stop(1) was called (check-ints-equal *(edx+4) 2 "F - test-function-with-addr-output: exit status") # don't restore from ebp 81 0/subop/add %esp 8/imm32 # . epilogue 5d/pop-to-ebp c3/return test-function-with-addr-inout: # . 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) (clear-stream _test-error-stream) (clear-stream $_test-error-buffered-file->buffer) # var ed/edx: exit-descriptor = tailor-exit-descriptor(16) 68/push 0/imm32 68/push 0/imm32 89/<- %edx 4/r32/esp (tailor-exit-descriptor %edx 0x10) # (write _test-input-stream "fn foo a: (addr addr int) {\n") (write _test-input-stream "}\n") # convert (convert-mu _test-input-buffered-file _test-output-buffered-file _test-error-buffered-file %edx) # registers except esp clobbered at this point # restore ed 89/<- %edx 4/r32/esp (flush _test-output-buffered-file) (flush _test-error-buffered-file) #? # dump _test-error-stream {{{ #? (write 2 "^") #? (write-stream 2 _test-error-stream) #? (write 2 "$\n") #? (rewind-stream _test-error-stream) #? # }}} # check output (check-stream-equal _test-output-stream ""
# some utilities for converting numbers from hex
# lowercase letters only for now
== code
# instruction effective address register displacement immediate
# . op subop mod rm32 base index scale r32
# . 1-3 bytes 3 bits 2 bits 3 bits 3 bits 3 bits 2 bits 2 bits 0/1/2/4 bytes 0/1/2/4 bytes
is-hex-int?: # in: (addr slice) -> result/eax: boolean
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
51/push-ecx
52/push-edx
53/push-ebx
# ecx = s
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 1/r32/ecx 8/disp8 . # copy *(ebp+8) to ecx
# edx = s->end
8b/copy 1/mod/*+disp8 1/rm32/ecx . . . 2/r32/edx 4/disp8 . # copy *(ecx+4) to edx
# var curr/ecx: (addr byte) = s->start
8b/copy 0/mod/indirect 1/rm32/ecx . . . 1/r32/ecx . . # copy *ecx to ecx
# if s is empty return false
b8/copy-to-eax 0/imm32/false
39/compare 3/mod/direct 1/rm32/ecx . . . 2/r32/edx . . # compare ecx with edx
73/jump-if-addr>= $is-hex-int?:end/disp8
# skip past leading '-'
# . if (*curr == '-') ++curr
31/xor 3/mod/direct 3/rm32/ebx . . . 3/r32/ebx . . # clear ebx
8a/copy-byte 0/mod/indirect 1/rm32/ecx . . . 3/r32/BL . . # copy byte at *ecx to BL
81 7/subop/compare 3/mod/direct 3/rm32/ebx . . . . . 0x2d/imm32/- # compare ebx
75/jump-if-!= $is-hex-int?:initial-0/disp8
# . ++curr
41/increment-ecx
# skip past leading '0x'
$is-hex-int?:initial-0:
# . if (*curr != '0') jump to loop
31/xor 3/mod/direct 3/rm32/ebx . . . 3/r32/ebx . . # clear ebx
8a/copy-byte 0/mod/indirect 1/rm32/ecx . . . 3/r32/BL . . # copy byte at *ecx to BL
81 7/subop/compare 3/mod/direct 3/rm32/ebx . . . . . 0x30/imm32/0 # compare ebx
75/jump-if-!= $is-hex-int?:loop/disp8
# . ++curr
41/increment-ecx
$is-hex-int?:initial-0x:
# . if (curr >= in->end) return true
39/compare 3/mod/direct 1/rm32/ecx . . . 2/r32/edx . . # compare ecx with edx
73/jump-if-addr>= $is-hex-int?:true/disp8
# . if (*curr != 'x') jump to loop # the previous '0' is still valid so doesn't need to be checked again
31/xor 3/mod/direct 3/rm32/ebx . . . 3/r32/ebx . . # clear ebx
8a/copy-byte 0/mod/indirect 1/rm32/ecx . . . 3/r32/BL . . # copy byte at *ecx to BL
81 7/subop/compare 3/mod/direct 3/rm32/ebx . . . . . 0x78/imm32/x # compare ebx
75/jump-if-!= $is-hex-int?:loop/disp8
# . ++curr
41/increment-ecx
$is-hex-int?:loop:
# if (curr >= in->end) return true
39/compare 3/mod/direct 1/rm32/ecx . . . 2/r32/edx . . # compare ecx with edx
73/jump-if-addr>= $is-hex-int?:true/disp8
# var eax: boolean = is-hex-digit?(*curr)
# . . push args
8a/copy-byte 0/mod/indirect 1/rm32/ecx . . . 0/r32/AL . . # copy byte at *ecx to AL
50/push-eax
# . . call
e8/call is-hex-digit?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# if (eax == false) return false
3d/compare-eax-and 0/imm32/false
74/jump-if-= $is-hex-int?:end/disp8
# ++curr
41/increment-ecx
# loop
eb/jump $is-hex-int?:loop/disp8
$is-hex-int?:true:
# return true
b8/copy-to-eax 1/imm32/true
$is-hex-int?:end:
# . restore registers
5b/pop-to-ebx
5a/pop-to-edx
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-is-hex-int:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "34"
b8/copy-to-eax "34"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = is-hex-int?(slice)
# . . push args
51/push-ecx
# . . call
e8/call is-hex-int?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-is-hex-int"/imm32
68/push 1/imm32/true
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-is-hex-int-handles-letters:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "34a"
b8/copy-to-eax "34a"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = is-hex-int?(slice)
# . . push args
51/push-ecx
# . . call
e8/call is-hex-int?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-is-hex-int-handles-letters"/imm32
68/push 1/imm32/true
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-is-hex-int-with-trailing-char:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "34q"
b8/copy-to-eax "34q"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = is-hex-int?(slice)
# . . push args
51/push-ecx
# . . call
e8/call is-hex-int?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-is-hex-int-with-trailing-char"/imm32
68/push 0/imm32/false
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-is-hex-int-with-leading-char:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "q34"
b8/copy-to-eax "q34"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = is-hex-int?(slice)
# . . push args
51/push-ecx
# . . call
e8/call is-hex-int?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-is-hex-int-with-leading-char"/imm32
68/push 0/imm32/false
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-is-hex-int-empty:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# var slice/ecx: slice = ""
68/push 0/imm32
68/push 0/imm32
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = is-hex-int?(slice)
# . . push args
51/push-ecx
# . . call
e8/call is-hex-int?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-is-hex-int-empty"/imm32
68/push 0/imm32/false
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-is-hex-int-handles-0x-prefix:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "0x3a"
b8/copy-to-eax "0x3a"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = is-hex-int?(slice)
# . . push args
51/push-ecx
# . . call
e8/call is-hex-int?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-is-hex-int-handles-0x-prefix"/imm32
68/push 1/imm32/true
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-is-hex-int-handles-negative:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "-34a"
b8/copy-to-eax "-34a"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = is-hex-int?(slice)
# . . push args
51/push-ecx
# . . call
e8/call is-hex-int?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-is-hex-int-handles-negative"/imm32
68/push 1/imm32/true
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-is-hex-int-handles-negative-0x-prefix:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "-0x3a"
b8/copy-to-eax "-0x3a"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = is-hex-int?(slice)
# . . push args
51/push-ecx
# . . call
e8/call is-hex-int?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-is-hex-int-handles-negative-0x-prefix"/imm32
68/push 1/imm32/true
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
parse-hex-int: # in: (addr array byte) -> result/eax: int
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
51/push-ecx
52/push-edx
# eax = in
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 0/r32/eax 8/disp8 . # copy *(ebp+8) to eax
# var curr/ecx: (addr byte) = &in->data
8d/copy-address 1/mod/*+disp8 0/rm32/eax . . . 1/r32/ecx 4/disp8 . # copy eax+4 to ecx
# var max/edx: (addr byte) = &in->data[in->size]
# . edx = in->size
8b/copy 0/mod/indirect 0/rm32/eax . . . 2/r32/edx . . # copy *eax to edx
# . edx = in->data + in->size
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 2/index/edx . 2/r32/edx 4/disp8 . # copy eax+edx+4 to edx
# return parse-hex-int-helper(curr, max)
# . . push args
52/push-edx
51/push-ecx
# . . call
e8/call parse-hex-int-helper/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
$parse-hex-int:end:
# . restore registers
5a/pop-to-edx
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
parse-hex-int-from-slice: # in: (addr slice) -> result/eax: int
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
51/push-ecx
52/push-edx
# ecx = in
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 1/r32/ecx 8/disp8 . # copy *(ebp+8) to ecx
# var max/edx: (addr byte) = in->end
8b/copy 1/mod/*+disp8 1/rm32/ecx . . . 2/r32/edx 4/disp8 . # copy *(ecx+4) to edx
# var curr/ecx: (addr byte) = in->start
8b/copy 0/mod/indirect 1/rm32/ecx . . . 1/r32/ecx . . # copy *ecx to ecx
# return parse-hex-int-helper(curr, max)
# . . push args
52/push-edx
51/push-ecx
# . . call
e8/call parse-hex-int-helper/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
$parse-hex-int-from-slice:end:
# . restore registers
5a/pop-to-edx
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
parse-hex-int-helper: # start: (addr byte), end: (addr byte) -> result/eax: int
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
51/push-ecx
52/push-edx
53/push-ebx
56/push-esi
# var curr/ecx: (addr byte) = start
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 1/r32/ecx 8/disp8 . # copy *(ebp+8) to ecx
# edx = max
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 2/r32/edx 0xc/disp8 . # copy *(ebp+12) to edx
# var result/ebx: int = 0
31/xor 3/mod/direct 3/rm32/ebx . . . 3/r32/ebx . . # clear ebx
# var negate?/esi: boolean = false
31/xor 3/mod/direct 6/rm32/esi . . . 6/r32/esi . . # clear esi
$parse-hex-int-helper:negative:
# if (*curr == '-') ++curr, negate = true
31/xor 3/mod/direct 0/rm32/eax . . . 0/r32/eax . . # clear eax
8a/copy-byte 0/mod/indirect 1/rm32/ecx . . . 0/r32/AL . . # copy byte at *ecx to AL
3d/compare-eax-and 0x2d/imm32/-
75/jump-if-!= $parse-hex-int-helper:initial-0/disp8
# . ++curr
41/increment-ecx
# . negate = true
be/copy-to-esi 1/imm32/true
$parse-hex-int-helper:initial-0:
# skip past leading '0x'
# . if (*curr != '0') jump to loop
8a/copy-byte 0/mod/indirect 1/rm32/ecx . . . 0/r32/AL . . # copy byte at *ecx to AL
3d/compare-eax-and 0x30/imm32/0
75/jump-if-!= $parse-hex-int-helper:loop/disp8
# . ++curr
41/increment-ecx
$parse-hex-int-helper:initial-0x:
# . if (curr >= in->end) return result
39/compare 3/mod/direct 1/rm32/ecx . . . 2/r32/edx . . # compare ecx with edx
73/jump-if-addr>= $parse-hex-int-helper:end/disp8
# . if (*curr != 'x') jump to loop # the previous '0' is still valid so doesn't need to be checked again
31/xor 3/mod/direct 0/rm32/eax . . . 0/r32/eax . . # clear eax
8a/copy-byte 0/mod/indirect 1/rm32/ecx . . . 0/r32/AL . . # copy byte at *ecx to AL
3d/compare-eax-and 0x78/imm32/x
75/jump-if-!= $parse-hex-int-helper:loop/disp8
# . ++curr
41/increment-ecx
$parse-hex-int-helper:loop:
# if (curr >= in->end) break
39/compare 3/mod/direct 1/rm32/ecx . . . 2/r32/edx . . # compare ecx with edx
73/jump-if-addr>= $parse-hex-int-helper:negate/disp8
# var eax: int = from-hex-char(*curr)
# . . copy arg to eax
8a/copy-byte 0/mod/indirect 1/rm32/ecx . . . 0/r32/AL . . # copy byte at *ecx to AL
# . . call
e8/call from-hex-char/disp32
# result = result * 16 + eax
c1/shift 4/subop/left 3/mod/direct 3/rm32/ebx . . . . . 4/imm8 # shift ebx left by 4 bits
01/add 3/mod/direct 3/rm32/ebx . . . 0/r32/eax . . # add eax to ebx
# ++curr
41/increment-ecx
# loop
eb/jump $parse-hex-int-helper:loop/disp8
$parse-hex-int-helper:negate:
# if (negate?) result = -result
81 7/subop/compare 3/mod/direct 6/rm32/esi . . . . . 0/imm32/false # compare esi
74/jump-if-= $parse-hex-int-helper:end/disp8
f7 3/subop/negate 3/mod/direct 3/rm32/ebx . . . . . . # negate ebx
$parse-hex-int-helper:end:
# return result
89/copy 3/mod/direct 0/rm32/eax . . . 3/r32/ebx . . # copy ebx to eax
# . restore registers
5e/pop-to-esi
5b/pop-to-ebx
5a/pop-to-edx
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-parse-hex-int-from-slice-single-digit:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "a"
b8/copy-to-eax "a"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = parse-hex-int-from-slice(slice)
# . . push args
51/push-ecx
# . . call
e8/call parse-hex-int-from-slice/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0xa, msg)
# . . push args
68/push "F - test-parse-hex-int-from-slice-single-digit"/imm32
68/push 0xa/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-parse-hex-int-from-slice-multi-digit:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "34a"
b8/copy-to-eax "34a"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = parse-hex-int-from-slice(slice)
# . . push args
51/push-ecx
# . . call
e8/call parse-hex-int-from-slice/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0x34a, msg)
# . . push args
68/push "F - test-parse-hex-int-from-slice-multi-digit"/imm32
68/push 0x34a/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-parse-hex-int-from-slice-0x-prefix:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "0x34"
b8/copy-to-eax "0x34"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = parse-hex-int-from-slice(slice)
# . . push args
51/push-ecx
# . . call
e8/call parse-hex-int-from-slice/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0x34, msg)
# . . push args
68/push "F - test-parse-hex-int-from-slice-0x-prefix"/imm32
68/push 0x34/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-parse-hex-int-from-slice-zero:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "0"
b8/copy-to-eax "0"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = parse-hex-int-from-slice(slice)
# . . push args
51/push-ecx
# . . call
e8/call parse-hex-int-from-slice/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-parse-hex-int-from-slice-zero"/imm32
68/push 0/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-parse-hex-int-from-slice-0-prefix:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "03"
b8/copy-to-eax "03"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = parse-hex-int-from-slice(slice)
# . . push args
51/push-ecx
# . . call
e8/call parse-hex-int-from-slice/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0x3, msg)
# . . push args
68/push "F - test-parse-hex-int-from-slice-0-prefix"/imm32
68/push 0x3/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-parse-hex-int-from-slice-negative:
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# (eax..ecx) = "-03"
b8/copy-to-eax "-03"/imm32
8b/copy 0/mod/indirect 0/rm32/eax . . . 1/r32/ecx . . # copy *eax to ecx
8d/copy-address 1/mod/*+disp8 4/rm32/sib 0/base/eax 1/index/ecx . 1/r32/ecx 4/disp8 . # copy eax+ecx+4 to ecx
05/add-to-eax 4/imm32
# var slice/ecx: slice = {eax, ecx}
51/push-ecx
50/push-eax
89/copy 3/mod/direct 1/rm32/ecx . . . 4/r32/esp . . # copy esp to ecx
# eax = parse-hex-int-from-slice(slice)
# . . push args
51/push-ecx
# . . call
e8/call parse-hex-int-from-slice/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, -3, msg)
# . . push args
68/push "F - test-parse-hex-int-from-slice-negative"/imm32
68/push -3/imm32
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
is-hex-digit?: # c: byte -> result/eax: boolean
# . prologue
55/push-ebp
89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp
# . save registers
51/push-ecx
# ecx = c
8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 1/r32/ecx 8/disp8 . # copy *(ebp+8) to ecx
# return false if c < '0'
81 7/subop/compare 3/mod/direct 1/rm32/ecx . . . . . 0x30/imm32 # compare ecx
7c/jump-if-< $is-hex-digit?:false/disp8
# return true if c <= '9'
81 7/subop/compare 3/mod/direct 1/rm32/ecx . . . . . 0x39/imm32 # compare ecx
7e/jump-if-<= $is-hex-digit?:true/disp8
# drop case
25/and-eax-with 0x5f/imm32
# return false if c > 'f'
81 7/subop/compare 3/mod/direct 1/rm32/ecx . . . . . 0x66/imm32 # compare ecx
7f/jump-if-> $is-hex-digit?:false/disp8
# return true if c >= 'a'
81 7/subop/compare 3/mod/direct 1/rm32/ecx . . . . . 0x61/imm32 # compare ecx
7d/jump-if->= $is-hex-digit?:true/disp8
# otherwise return false
$is-hex-digit?:false:
b8/copy-to-eax 0/imm32/false
eb/jump $is-hex-digit?:end/disp8
$is-hex-digit?:true:
b8/copy-to-eax 1/imm32/true
$is-hex-digit?:end:
# . restore registers
59/pop-to-ecx
# . epilogue
89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp
5d/pop-to-ebp
c3/return
test-hex-below-0:
# eax = is-hex-digit?(0x2f)
# . . push args
68/push 0x2f/imm32
# . . call
e8/call is-hex-digit?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-hex-below-0"/imm32
68/push 0/imm32/false
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
c3/return
test-hex-0-to-9:
# eax = is-hex-digit?(0x30)
# . . push args
68/push 0x30/imm32
# . . call
e8/call is-hex-digit?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-hex-at-0"/imm32
68/push 1/imm32/true
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# eax = is-hex-digit?(0x39)
# . . push args
68/push 0x39/imm32
# . . call
e8/call is-hex-digit?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-hex-at-9"/imm32
68/push 1/imm32/true
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
c3/return
test-hex-above-9-to-a:
# eax = is-hex-digit?(0x3a)
# . . push args
68/push 0x3a/imm32
# . . call
e8/call is-hex-digit?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-hex-above-9-to-a"/imm32
68/push 0/imm32/false
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
c3/return
test-hex-a-to-f:
# eax = is-hex-digit?(0x61)
# . . push args
68/push 0x61/imm32
# . . call
e8/call is-hex-digit?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-hex-at-a"/imm32
68/push 1/imm32/true
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
# eax = is-hex-digit?(0x66)
# . . push args
68/push 0x66/imm32
# . . call
e8/call is-hex-digit?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 1, msg)
# . . push args
68/push "F - test-hex-at-f"/imm32
68/push 1/imm32/true
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
c3/return
test-hex-above-f:
# eax = is-hex-digit?(0x67)
# . . push args
68/push 0x67/imm32
# . . call
e8/call is-hex-digit?/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# check-ints-equal(eax, 0, msg)
# . . push args
68/push "F - test-hex-above-f"/imm32
68/push 0/imm32/false
50/push-eax
# . . call
e8/call check-ints-equal/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp
c3/return
from-hex-char: # in/eax: byte -> out/eax: nibble
$from-hex-char:check0:
# if (eax < '0') goto abort
3d/compare-eax-with 0x30/imm32/0
7c/jump-if-< $from-hex-char:abort/disp8
$from-hex-char:check1:
# if (eax > 'f') goto abort
3d/compare-eax-with 0x66/imm32/f
7f/jump-if-> $from-hex-char:abort/disp8
$from-hex-char:check2:
# if (eax > '9') goto next check
3d/compare-eax-with 0x39/imm32/9
7f/jump-if-> $from-hex-char:check3/disp8
$from-hex-char:digit:
# return eax - '0'
2d/subtract-from-eax 0x30/imm32/0
c3/return
$from-hex-char:check3:
# if (eax < 'a') goto abort
3d/compare-eax-with 0x61/imm32/a
7c/jump-if-< $from-hex-char:abort/disp8
$from-hex-char:letter:
# return eax - ('a'-10)
2d/subtract-from-eax 0x57/imm32/a-10
c3/return
$from-hex-char:abort:
# . _write(2/stderr, error)
# . . push args
68/push "invalid hex char: "/imm32
68/push 2/imm32/stderr
# . . call
e8/call _write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# . clear-stream($Stderr->buffer)
# . . save eax
50/push-eax
# . . push args
68/push $Stderr->buffer/imm32
# . . call
e8/call clear-stream/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . . restore eax
58/pop-to-eax
# . write-int32-hex-buffered(Stderr, eax)
# . . push args
50/push-eax
68/push Stderr/imm32
# . . call
e8/call write-int32-hex-buffered/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp
# . _write(2/stderr, "\n")
# . . push args
68/push Newline/imm32
68/push 2/imm32/stderr
# . . call
e8/call _write/disp32
# . . discard args
81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp
# . syscall(exit, 1)
bb/copy-to-ebx 1/imm32
e8/call syscall_exit/disp32
# never gets here
# . . vim:nowrap:textwidth=0