6094 - new 'compute-offset' instruction

If indexing into a type with power-of-2-sized elements we can access them
in one instruction:

  x/reg1: (addr int) <- index A/reg2: (addr array int), idx/reg3: int

This translates to a single instruction because x86 instructions support
an addressing mode with left-shifts.

For non-powers-of-2, however, we need a multiply. To keep things type-safe,
it is performed like this:

  x/reg1: (offset T) <- compute-offset A: (addr array T), idx: int
  y/reg2: (addr T) <- index A, x

An offset is just an int that is guaranteed to be a multiple of size-of(T).
Offsets can only be used in index instructions, and the types will eventually
be required to line up.

In the process, I have to expand Input-size because mu.subx is growing
big.
This commit is contained in:
Kartik Agaram 2020-03-07 17:32:39 -08:00
parent 9ee4b34e06
commit 3cf0315859
15 changed files with 186 additions and 29 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
apps/hex

Binary file not shown.

BIN
apps/mu

Binary file not shown.

View File

@ -389,6 +389,7 @@ Type-id: # (stream (address array byte))
"handle"/imm32 # 4 "handle"/imm32 # 4
"boolean"/imm32 # 5 "boolean"/imm32 # 5
"constant"/imm32 # 6: like a literal, but replaced with its value in Var-offset "constant"/imm32 # 6: like a literal, but replaced with its value in Var-offset
"offset"/imm32 # 7: (offset T) is guaranteed to be a 32-bit multiple of size-of(T)
0/imm32 0/imm32
# 0x20 # 0x20
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
@ -2311,6 +2312,58 @@ test-convert-index-into-array-with-literal:
5d/pop-to-ebp 5d/pop-to-ebp
c3/return c3/return
test-convert-index-into-array-using-offset:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
(clear-stream _test-input-stream)
(clear-stream $_test-input-buffered-file->buffer)
(clear-stream _test-output-stream)
(clear-stream $_test-output-buffered-file->buffer)
c7 0/subop/copy *Next-block-index 1/imm32
#
(write _test-input-stream "fn foo {\n")
(write _test-input-stream " var arr/eax: (addr array int) <- copy 0\n")
(write _test-input-stream " var idx/ecx: int <- copy 3\n")
(write _test-input-stream " var off/ecx: (offset int) <- compute-offset arr, idx\n")
(write _test-input-stream " var x/eax: (addr int) <- index arr, off\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-index-into-array-using-offset/0")
(check-next-stream-line-equal _test-output-stream " # . prologue" "F - test-convert-index-into-array-using-offset/1")
(check-next-stream-line-equal _test-output-stream " 55/push-ebp" "F - test-convert-index-into-array-using-offset/2")
(check-next-stream-line-equal _test-output-stream " 89/<- %ebp 4/r32/esp" "F - test-convert-index-into-array-using-offset/3")
(check-next-stream-line-equal _test-output-stream " {" "F - test-convert-index-into-array-using-offset/4")
(check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:" "F - test-convert-index-into-array-using-offset/5")
(check-next-stream-line-equal _test-output-stream " ff 6/subop/push %eax" "F - test-convert-index-into-array-using-offset/6")
(check-next-stream-line-equal _test-output-stream " b8/copy-to-eax 0/imm32" "F - test-convert-index-into-array-using-offset/7")
(check-next-stream-line-equal _test-output-stream " ff 6/subop/push %ecx" "F - test-convert-index-into-array-using-offset/8")
(check-next-stream-line-equal _test-output-stream " b9/copy-to-ecx 3/imm32" "F - test-convert-index-into-array-using-offset/9")
(check-next-stream-line-equal _test-output-stream " 69/multiply 0x00000004/imm32 %ecx 0x00000001/r32" "F - test-convert-index-into-array-using-offset/10")
(check-next-stream-line-equal _test-output-stream " 8d/copy-address *(eax + ecx + 4) 0x00000000/r32" "F - test-convert-index-into-array-using-offset/11")
(check-next-stream-line-equal _test-output-stream " 8f 0/subop/pop %ecx" "F - test-convert-index-into-array-using-offset/12")
(check-next-stream-line-equal _test-output-stream " 8f 0/subop/pop %eax" "F - test-convert-index-into-array-using-offset/13")
(check-next-stream-line-equal _test-output-stream " }" "F - test-convert-index-into-array-using-offset/14")
(check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:" "F - test-convert-index-into-array-using-offset/15")
(check-next-stream-line-equal _test-output-stream " # . epilogue" "F - test-convert-index-into-array-using-offset/16")
(check-next-stream-line-equal _test-output-stream " 89/<- %esp 5/r32/ebp" "F - test-convert-index-into-array-using-offset/17")
(check-next-stream-line-equal _test-output-stream " 5d/pop-to-ebp" "F - test-convert-index-into-array-using-offset/18")
(check-next-stream-line-equal _test-output-stream " c3/return" "F - test-convert-index-into-array-using-offset/19")
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-convert-function-and-type-definition: test-convert-function-and-type-definition:
# . prologue # . prologue
55/push-ebp 55/push-ebp
@ -6684,6 +6737,15 @@ emit-subx-stmt: # out: (addr buffered-file), stmt: (handle stmt), primitives: (
(translate-mu-index-stmt *(ebp+8) *(ebp+0xc)) (translate-mu-index-stmt *(ebp+8) *(ebp+0xc))
e9/jump $emit-subx-stmt:end/disp32 e9/jump $emit-subx-stmt:end/disp32
} }
# compute-offset for index into array
{
# if (!string-equal?(var->operation, "compute-offset")) break
(string-equal? *(ecx+4) "compute-offset") # Stmt1-operation => eax
3d/compare-eax-and 0/imm32
0f 84/jump-if-= break/disp32
(translate-mu-compute-index-stmt *(ebp+8) *(ebp+0xc))
e9/jump $emit-subx-stmt:end/disp32
}
# get field from record # get field from record
{ {
# if (!string-equal?(var->operation, "get")) break # if (!string-equal?(var->operation, "get")) break
@ -6788,23 +6850,46 @@ $translate-mu-index-stmt:emit-base:
81 7/subop/compare *(edx+0x10) 0/imm32 # Var-register 81 7/subop/compare *(edx+0x10) 0/imm32 # Var-register
{ {
0f 84/jump-if-= break/disp32 0f 84/jump-if-= break/disp32
# print inouts[1]->register "<<" log2(sizeof(element(inouts[0]->type))) " + 4) "
$translate-mu-index-stmt:emit-register-index: $translate-mu-index-stmt:emit-register-index:
# . inouts[1]->register "<<" # if inouts[1] is an int
(write-buffered *(ebp+8) *(edx+0x10)) # Var-register (is-simple-mu-type? *(edx+4) 1) # Var-type, int => eax
(write-buffered *(ebp+8) "<<") 3d/compare-eax-and 0/imm32/false
# . log2(sizeof(element(inouts[0]->type))) {
# TODO: ensure size is a power of 2 0f 84/jump-if-= break/disp32
(array-element-type-id %ebx) # => eax $translate-mu-index-stmt:emit-int-register-index:
(size-of-type-id %eax) # => eax # print inouts[1]->register "<<" log2(sizeof(element(inouts[0]->type))) " + 4) "
(num-shift-rights %eax) # => eax # . inouts[1]->register "<<"
(print-int32-buffered *(ebp+8) %eax) (write-buffered *(ebp+8) *(edx+0x10)) # Var-register
# . (write-buffered *(ebp+8) "<<")
# . log2(sizeof(element(inouts[0]->type)))
# TODO: ensure size is a power of 2
(array-element-type-id %ebx) # => eax
(size-of-type-id %eax) # => eax
(num-shift-rights %eax) # => eax
(print-int32-buffered *(ebp+8) %eax)
e9/jump $translate-mu-index-stmt:emit-register-index-done/disp32
}
# if inouts[1]->type is any other atom, abort
8b/-> *(edx+4) 0/r32/eax # Var-type
8b/-> *eax 0/r32/eax # Tree-left or Atom-value
3b/compare 0/r32/eax *Max-type-id
0f 82/jump-if-addr< $translate-mu-index-stmt:error2/disp32
# if inouts[1] is (offset ...)
(is-simple-mu-type? %eax 7) # offset => eax
3d/compare-eax-and 0/imm32/false
{
0f 84/jump-if-= break/disp32
# print inouts[1]->register "<<" log2(sizeof(element(inouts[0]->type))) " + 4) "
$translate-mu-index-stmt:emit-offset-register-index:
# . inouts[1]->register
(write-buffered *(ebp+8) *(edx+0x10)) # Var-register
}
$translate-mu-index-stmt:emit-register-index-done:
(write-buffered *(ebp+8) " + 4) ") (write-buffered *(ebp+8) " + 4) ")
e9/jump $translate-mu-index-stmt:emit-output/disp32 e9/jump $translate-mu-index-stmt:emit-output/disp32
} }
# otherwise if inouts[1] is a literal # otherwise if inouts[1] is a literal
(is-literal-type? *(edx+4)) # Var-type => eax (is-simple-mu-type? *(edx+4) 0) # Var-type => eax
3d/compare-eax-and 0/imm32/false 3d/compare-eax-and 0/imm32/false
{ {
0f 84/jump-if-= break/disp32 0f 84/jump-if-= break/disp32
@ -6825,7 +6910,7 @@ $translate-mu-index-stmt:emit-literal-index:
e9/jump $translate-mu-index-stmt:emit-output/disp32 e9/jump $translate-mu-index-stmt:emit-output/disp32
} }
# otherwise abort # otherwise abort
e9/jump $translate-mu-index-stmt:abort/disp32 e9/jump $translate-mu-index-stmt:error1/disp32
$translate-mu-index-stmt:emit-output: $translate-mu-index-stmt:emit-output:
# outputs[0] "/r32" # outputs[0] "/r32"
8b/-> *(ecx+0xc) 0/r32/eax # Stmt1-outputs 8b/-> *(ecx+0xc) 0/r32/eax # Stmt1-outputs
@ -6844,7 +6929,7 @@ $translate-mu-index-stmt:end:
5d/pop-to-ebp 5d/pop-to-ebp
c3/return c3/return
$translate-mu-index-stmt:abort: $translate-mu-index-stmt:error1:
(write-buffered Stderr "couldn't translate an index instruction. second (index) input must either lie in a register or be a literal\n") (write-buffered Stderr "couldn't translate an index instruction. second (index) input must either lie in a register or be a literal\n")
(flush Stderr) (flush Stderr)
# . syscall(exit, 1) # . syscall(exit, 1)
@ -6853,6 +6938,60 @@ $translate-mu-index-stmt:abort:
cd/syscall 0x80/imm8 cd/syscall 0x80/imm8
# never gets here # never gets here
$translate-mu-index-stmt:error2:
(write-buffered Stderr "couldn't translate an index instruction. second (index) input when in a register must be an int or offset\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
translate-mu-compute-index-stmt: # out: (address buffered-file), stmt: (handle stmt)
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
52/push-edx
53/push-ebx
#
(emit-indent *(ebp+8) *Curr-block-depth)
(write-buffered *(ebp+8) "69/multiply ")
$translate-mu-compute-index-stmt:emit-elem-size:
# ecx = stmt
8b/-> *(ebp+0xc) 1/r32/ecx
# var first-inout/edx: (handle stmt-var) = stmt->inouts[0]
8b/-> *(ecx+8) 2/r32/edx # Stmt1-inouts
# var base/ebx: (handle var)
8b/-> *edx 3/r32/ebx # Stmt-var-value
# print sizeof(element(base->type))
(array-element-type-id %ebx) # => eax
(size-of-type-id %eax) # => eax
(print-int32-buffered *(ebp+8) %eax)
(write-buffered *(ebp+8) "/imm32")
$translate-mu-compute-index-stmt:emit-index:
(emit-subx-var-as-rm32 *(ebp+8) *(edx+4)) # Stmt-var-next
(write-buffered *(ebp+8) Space)
$translate-mu-compute-index-stmt:emit-output:
# outputs[0] "/r32"
8b/-> *(ecx+0xc) 0/r32/eax # Stmt1-outputs
8b/-> *eax 0/r32/eax # Stmt-var-value
(get Registers *(eax+0x10) 8 "Registers") # Var-register => eax
(print-int32-buffered *(ebp+8) *eax)
(write-buffered *(ebp+8) "/r32\n")
$translate-mu-compute-index-stmt:end:
# . 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
translate-mu-get-stmt: # out: (address buffered-file), stmt: (handle stmt) translate-mu-get-stmt: # out: (address buffered-file), stmt: (handle stmt)
# . prologue # . prologue
55/push-ebp 55/push-ebp
@ -9114,10 +9253,10 @@ subx-type-equal?: # a: (handle tree type-id), b: (handle tree type-id) -> resul
# . save registers # . save registers
51/push-ecx 51/push-ecx
# var alit/ecx: boolean = is-literal-type?(a) # var alit/ecx: boolean = is-literal-type?(a)
(is-literal-type? *(ebp+8)) # => eax (is-simple-mu-type? *(ebp+8) 0) # => eax
89/<- %ecx 0/r32/eax 89/<- %ecx 0/r32/eax
# var blit/eax: boolean = is-literal-type?(b) # var blit/eax: boolean = is-literal-type?(b)
(is-literal-type? *(ebp+0xc)) # => eax (is-simple-mu-type? *(ebp+0xc) 0) # => eax
# return alit == blit # return alit == blit
39/compare %eax 1/r32/ecx 39/compare %eax 1/r32/ecx
0f 94/set-if-= %eax 0f 94/set-if-= %eax
@ -9130,17 +9269,22 @@ $subx-type-equal?:end:
5d/pop-to-ebp 5d/pop-to-ebp
c3/return c3/return
is-literal-type?: # a: (handle tree type-id) -> result/eax: boolean is-simple-mu-type?: # a: (handle tree type-id), n: type-id -> result/eax: boolean
# . prologue # . prologue
55/push-ebp 55/push-ebp
89/<- %ebp 4/r32/esp 89/<- %ebp 4/r32/esp
# # . save registers
51/push-ecx
# ecx = n
8b/-> *(ebp+0xc) 1/r32/ecx
# return (a->value == n)
8b/-> *(ebp+8) 0/r32/eax 8b/-> *(ebp+8) 0/r32/eax
# return (*eax == 0) 39/compare *eax 1/r32/ecx # Atom-type
81 7/subop/compare *eax 0/imm32/literal-type-id # Atom-type
0f 94/set-if-= %eax 0f 94/set-if-= %eax
81 4/subop/and %eax 0xff/imm32 81 4/subop/and %eax 0xff/imm32
$is-literal-type?:end: $is-simple-mu-type?:end:
# . restore registers
59/pop-to-ecx
# . epilogue # . epilogue
89/<- %esp 5/r32/ebp 89/<- %esp 5/r32/ebp
5d/pop-to-ebp 5d/pop-to-ebp

BIN
apps/pack

Binary file not shown.

Binary file not shown.

View File

@ -8,7 +8,7 @@ Segment-size:
# maximum size of input textual stream (spanning all segments) # maximum size of input textual stream (spanning all segments)
Input-size: Input-size:
0x180000/imm32/1.5MB 0x200000/imm32/2MB
# number of labels we can translate to addresses # number of labels we can translate to addresses
Max-labels: Max-labels:

Binary file not shown.

View File

@ -115,7 +115,7 @@ $subx-survey-main:end:
subx-survey: # infile: (addr buffered-file), out: (addr buffered-file) subx-survey: # infile: (addr buffered-file), out: (addr buffered-file)
# pseudocode # pseudocode
# var in: (stream byte 4096) # var in: (stream byte Input-size)
# slurp(infile, in) # slurp(infile, in)
# var segments: (stream segment-info) # var segments: (stream segment-info)
# var labels: (stream label-info Max-labels) # var labels: (stream label-info Max-labels)

Binary file not shown.

View File

@ -207,13 +207,21 @@ loop-if-addr>= label {.name="loop-if-addr>=", .inouts=[label],
Array operations Array operations
var/reg <- length var2/reg2: (addr array T) var/reg <- length arr/reg2: (addr array T)
{.name="length", .inouts=[reg2], .outputs=[reg1], .subx-name="8b/copy-from", .rm32="*" inouts[0], .r32=outputs[0]} {.name="length", .inouts=[reg2], .outputs=[reg1], .subx-name="8b/copy-from", .rm32="*" inouts[0], .r32=outputs[0]}
var/reg <- index arr/rega: (addr array T), idx/regi: int var/reg <- index arr/rega: (addr array T), idx/regi: int
{.name="index", .inouts=[rega, regi], .outputs=[reg], .subx-name="8d/copy-address", .rm32="*(" inouts[0] "+" inouts[1] "<<2)", .r32=outputs[0]} {.name="index", .inouts=[rega, regi], .outputs=[reg], .subx-name="8d/copy-address", .rm32="*(" inouts[0] "+" inouts[1] "<<2)", .r32=outputs[0]}
var/reg <- index arr/rega: (addr array T), n var/reg <- index arr/rega: (addr array T), n
compare var, n {.name="compare", .inouts=[var, n], .subx-name="81 7/subop/compare", .rm32="*(ebp+" inouts[0].stack-offset ")", .imm32=inouts[1]} {.name="index", .inouts=[rega, n], .outputs=[reg], .subx-name="8d/copy-address", .rm32="*(" inouts[0] "+" inouts[1]*size(T) ")", .r32=outputs[0]}
{.name="index", .inouts=[rega, n], .outputs=[reg], .subx-name="8d/copy-address", .rm32="*(" inouts[0] "+" inouts[1] "<<2)", .r32=outputs[0]}
var/reg: (offset T) <- compute-offset arr: (addr array T), idx/regi: int # arr can be in reg or mem
{.name="compute-offset", .inouts=[arr, regi], .outputs=[reg], .subx-name="69/multiply", .rm32=inouts[1], .r32=outputs[0], .imm32=sizeof(T)}
var/reg: (offset T) <- compute-offset arr: (addr array T), idx: int # arr can be in reg or mem
{.name="compute-offset", .inouts=[arr, regi], .outputs=[reg], .subx-name="69/multiply", .rm32="*(ebp+" inouts[1].stack-offset ")", .r32=outputs[0], .imm32=sizeof(T)}
var: (offset T) <- compute-offset arr: (addr array T), n # arr can be in reg or mem
{.name="compute-offset", .inouts=[var, n], .outputs=[reg], .subx-name="c7 0/subop/copy", .rm32=outputs[0], .imm32=sizeof(T)*n}
var/reg <- index arr/rega: (addr array T), o/rego: offset
{.name="index", .inouts=[rega, rego], .outputs=[reg], .subx-name="8d/copy-address", .rm32="*(" inouts[0] "+" inouts[1] "+" "4)", .r32=outputs[0]}
User-defined types User-defined types

View File

@ -197,9 +197,14 @@ Similarly, conditional loops:
## Array operations ## Array operations
var/reg: int <- length var: (addr array T) var/reg: int <- length arr/reg: (addr array T)
var/reg: (addr T) <- index var: (addr array T), idx: int var/reg: (addr T) <- index arr/reg: (addr array T), idx/reg: int
var/reg: (addr T) <- index var: (addr array T), n var/reg: (addr T) <- index arr/reg: (addr array T), n
var/reg: (offset T) <- compute-offset arr: (addr array T), idx/reg: int # arr can be in reg or mem
var/reg: (offset T) <- compute-offset arr: (addr array T), n # arr can be in reg or mem
var: (offset T) <- compute-offset arr: (addr array T), n # arr can be in reg or mem
var/reg: (addr T) <- index arr/reg: (addr array T), idx/reg: (offset T)
## User-defined types ## User-defined types