diff options
Diffstat (limited to 'include')
| -rw-r--r-- | include/apply.s | 19 | ||||
| -rw-r--r-- | include/data.s | 272 | ||||
| -rw-r--r-- | include/gc.s | 210 | ||||
| -rw-r--r-- | include/intops.s | 6 | ||||
| -rw-r--r-- | include/io.s | 4 | ||||
| -rw-r--r-- | include/listops.s | 2 | ||||
| -rw-r--r-- | include/main_exit.s | 7 | ||||
| -rw-r--r-- | include/primops.s | 2 | ||||
| -rw-r--r-- | include/uskel.s | 52 |
9 files changed, 474 insertions, 100 deletions
diff --git a/include/apply.s b/include/apply.s index 82465bc..7c5d366 100644 --- a/include/apply.s +++ b/include/apply.s @@ -5,6 +5,7 @@ # | fun | arg | -> cont .thunkcode apply1 + needs_alloc $050 thunkto %rsi, $apply1_fini, $3, 030(%rbp), %rbp, %rsi enter 020(%rbp) # evaluate fun @@ -14,6 +15,10 @@ # we're certainly going to copy a lot of args. mov 020(%rsi), %r11 # amount of args applied now + # prepare enough memory for the worst case alloc (make FUN from arg count + 3) + lea 030(,%r11,010), %r12 + needs_alloc %r12 + # the copying code is shared so let's do that first: pushq 020(%rbp) #push the new arg lea 030(%rsi), %rdx # the end (first arg) @@ -59,6 +64,7 @@ apply1_fini_feed: # | fun | arg[1] | arg[2] | ... | arg[args-1] | -> cont .thunkcode apply + needs_alloc $040 thunkto %rsi, $apply_fini, $2, %rbp, %rsi enter 020(%rbp) @@ -71,13 +77,18 @@ apply1_fini_feed: mov 010(%r10), %r13 # amount of args in the original thunk sub $1, %r13 # amount of args we want to apply (the 1st one is the FUN) - mov %r11, %r14 - add %r13, %r14 - cmp %r12, %r14 # do we have enough arguments? + lea (%r11, %r13), %r14 # total amount arguments we have + lea 050(%r14), %r15 # how much memory this needs in extreme + needs_alloc %r15 + # worst-case memory is: we make a thunk (2 headers + some args) and a + # leftover closure (3 headers + rest of args) + + # Now that we have enough memory, do we have enough arguments to do anything? + cmp %r12, %r14 ja apply_fini_o apply_fini_pt: - # not enough args or exactly enough args. + # Not enough args or exactly enough args. # Basically make a function or a thunk that is almost the same. # first move thunk params diff --git a/include/data.s b/include/data.s index 2c512d4..35c4c8d 100644 --- a/include/data.s +++ b/include/data.s @@ -1,14 +1,7 @@ .ifndef _data_s_file _data_s_file: - -# Simple values and boxed machine integers -# | ptr | value | -CON_evacuate1: - retq # TODO -CON_scavenge1: - add $020, %rsi - retq + nop # avoid confusing gdb # Format of the info tables: # - code @@ -16,27 +9,160 @@ CON_scavenge1: # - 8B helper information for eval/apply (generally this is 0, and only gets used for FUN/PAP) # - 8B pointer to scavenge # - 8B pointer to evacuate +# +# Evacuate interface: +# in: %rbp what to evacuate +# out: %rbp where the thing is now +# ret: jump to _gc_evacuate_ret +# Notes: +# - IND thunks skip themselves on evacuate +# - checking if whether stuff is already in write region are managed by +# _gc_evacuate prelude +# +# Scavenge interface: +# in: %rbp what to scavenge +# out: %rbp next thing to scavenge in memory +# ret: jump to _gc_scavenge_ret +# +# Saved registers during the GC process: +# - _uskel_gc uses %r8-%r11 +# - scavenges use %r12-%r15 +# - %rax-%rdx is scratch and evacuate use + +# Simple values and boxed machine integers +# | ptr | value | +INT_evacuate: + pushq 010(%rbp) + pushq $INT_code + mov %rsp,%rbp + jmp _gc_evacuate_ret +INT_scavenge: + add $020, %rbp + jmp _gc_scavenge_ret INT_info_table: - cell CON_evacuate1 - cell CON_scavenge1 + cell INT_evacuate + cell INT_scavenge cell 0 INT_code: continue +# Indirection +# | ptr | indptr | +IND_evacuate: + mov 010(%rbp), %rbp + jmp _gc_evacuate +IND_scavenge: + # this should never be triggered but let's play it safe + add $020, %rbp + jmp _gc_scavenge_ret + +IND_info: + cell IND_evacuate + cell IND_scavenge + cell 0 +IND_code: + enter 010(%rbp) + +# Blackhole (contains the original thunkptr for debugging purposes) +# | ptr | orig_thunkptr | +BLE_evacuate: + jmp _gc_evacuate_ret +BLE_scavenge: + jmp _gc_scavenge_ret + +BLE_info_table: + cell BLE_evacuate + cell BLE_scavenge + cell 0 +BLE_code: + # if we hit this, we've got a pure loop in a program, and it is never + # going to actually progress. So let's just shoot it down with a + # helpful message or so. + mov 010(%rbp), %r15 + + mov $1, %rdi #stdout + + mov $14, %rdx + mov $BLE_msg, %rsi + mov $1, %rax #write + syscall + + mov $1, %rdx + BLE_loop: + mov %r15, %rcx + and $0xf, %rcx + mov $BLE_hex, %rsi + add %rcx, %rsi + mov $1, %rax + syscall + shr $4, %r15 + jnz BLE_loop + + mov $BLE_nl, %rsi + mov $1, %rax + syscall + + # shot self down (and retry if it doesn't succeed) + BLE_retry_sigkill: + mov $39, %rax + syscall # getpid + mov %rax, %rdi + mov $6, %rsi # SIGABRT + mov $62, %rax # kill + syscall + jmp BLE_retry_sigkill + +BLE_msg: + .ascii "diverged at 0x" +BLE_hex: + .ascii "0123456789abcdef" +BLE_nl: + .ascii "\n" + # List -# | ptr | 0 | -# | ptr | 1 | a | b | +# | ptr | 0 | # [] case +# | ptr | 1 | a | b | # (a:b) case LIST_evacuate: - # [] | a : b - retq #TODO + cmpq $0, 010(%rbp) + je LIST_evacuate_nil + pushq 030(%rbp) + pushq 020(%rbp) + pushq $1 + pushq $LIST_code + mov %rsp, %rbp + jmp _gc_evacuate_ret + + LIST_evacuate_nil: + pushq $0 + pushq $LIST_code + mov %rsp, %rbp + jmp _gc_evacuate_ret LIST_scavenge: - mov 010(%rbp), %rax - shl $1, %rax - add $2, %rax - shl $3, %rax - add %rax, %rsi - retq + cmpq $0, 010(%rbp) + je LIST_scavenge_nil + mov %rbp, %r15 + + mov $LIST_scavenge1, %rsi + mov 020(%r15), %rbp + jmp _gc_evacuate + LIST_scavenge1: + mov %rbp, 020(%r15) + + mov $LIST_scavenge2, %rsi + mov 030(%r15), %rbp + jmp _gc_evacuate + LIST_scavenge2: + mov %rbp, 030(%r15) + + mov %r15, %rbp + add $040, %rbp + jmp _gc_scavenge_ret + + LIST_scavenge_nil: + add $020, %rbp + jmp _gc_scavenge_ret + LIST_info_table: cell LIST_evacuate cell LIST_scavenge @@ -47,13 +173,42 @@ LIST_code: # FUN objects # | ptr | thunkptr | args | arg[0] | arg[1] | ... | arg[args] | FUN_evacuate: - retq #TODO + mov 020(%rbp), %rbx # rbx = count of arguments + mov %rbx, %rcx # rcx = count of arguments for later looping + lea 030(%rbp, %rbx, 010), %rdx # rdx = address of the arguments + FUN_evacuate_one: + sub $1, %rcx + jl FUN_evacuate_fini + sub $010, %rdx + pushq (%rdx) + jmp FUN_evacuate_one + FUN_evacuate_fini: + pushq %rbx + pushq 010(%rbp) + pushq 000(%rbp) + mov %rsp, %rbp + jmp _gc_evacuate_ret FUN_scavenge: - mov 020(%rbp), %rax - add $3, %rax - shl $3, %rax - add %rax, %rsi - retq + mov 020(%rbp), %r13 # r13 = count of arguments (for looping) + mov %rbp, %r15 # r15 = scavengee ptr + lea 030(%rbp, %r13, 010), %r14 # r14 = address of argument + + FUN_scavenge_one: + sub $1, %r13 + jl FUN_scavenge_fini + sub $010, %r14 + mov (%r14), %rbp + mov $FUN_scavenge_one_cont, %rsi + jmp _gc_evacuate + FUN_scavenge_one_cont: + mov %rbp, (%r14) + jmp FUN_scavenge_one + + FUN_scavenge_fini: + mov %r15, %rbp # restore rbp + mov 020(%rbp), %r14 + lea 030(%rbp, %r14, 010), %rbp + jmp _gc_scavenge_ret # Info tables for FUN objects. FUN0_info_table: @@ -90,39 +245,46 @@ FUN4_info_table: cell 4 FUN4_code: continue +# add more funN here as needed -# TODO: add more funN here as needed - -# indirection (Q: how to recognize IND and THUNK on return?) -# | ptr | indptr | -IND_evacuate: - retq #TODO -IND_scavenge: - add $020,%rsi - retq -IND_info: - cell IND_evacuate - cell IND_scavenge - cell 0 -IND_code: - enter 010(%rbp) - -# THU objects (gc implementation only, actual THUs are defined by functions) +# THU objects (gc implementation only, actual THU data are created by functions) # | ptr | args | arg[0] | arg[1] | ... | arg[args] | # args wouldn't need to be here but let's keep them for gc simplicity THU_evacuate: - retq #TODO + mov 010(%rbp), %rbx # rbx = count of arguments + mov %rbx, %rcx # rcx = count of arguments for later looping + lea 020(%rbp, %rbx, 010), %rdx # rdx = address of the argument + THU_evacuate_one: + sub $1, %rcx + jl THU_evacuate_fini + sub $010, %rdx + pushq (%rdx) + jmp THU_evacuate_one + THU_evacuate_fini: + pushq %rbx + pushq 000(%rbp) + mov %rsp, %rbp + jmp _gc_evacuate_ret THU_scavenge: - mov 010(%rbp), %rax - add $2,%rax - shl $3,%rax - add %rax,%rsi - retq + mov 010(%rbp), %r13 # r13 = count of arguments (for looping) + mov %rbp, %r15 # r15 = scavengee ptr + lea 020(%rbp, %r13, 010), %r14 # r14 = address of argument -.endif # _data_s_file + THU_scavenge_one: + sub $1, %r13 + jl THU_scavenge_fini + sub $010, %r14 + mov (%r14), %rbp + mov $THU_scavenge_one_cont, %rsi + jmp _gc_evacuate + THU_scavenge_one_cont: + mov %rbp, (%r14) + jmp THU_scavenge_one + + THU_scavenge_fini: + mov %r15, %rbp # restore rbp + mov 010(%rbp), %r14 + lea 020(%rbp, %r14, 010), %rbp + jmp _gc_scavenge_ret -# evacuate and scavenge: -# - evacuate just copies the object -# - scavenge evacuates all children (to the new location IF they are in the old -# location), changes the pointer, and moves the scavenge pointer to the next -# object (because everything needs to be scavenged) +.endif # _data_s_file diff --git a/include/gc.s b/include/gc.s new file mode 100644 index 0000000..b40ac95 --- /dev/null +++ b/include/gc.s @@ -0,0 +1,210 @@ + +.ifndef _gc_s_file +_gc_s_file: + +.section .bss +_write_region_start: + # begin of the active memory area + cell 0 +_write_region_end: + # end of the active memory area (%rsp kinda starts here and goes down + # towars the start) + cell 0 +_gc_trigger: + # point in memory where the gc will trigger (we don't necessarily wait for the write region to fill up!) + cell 0 + +_gc_last_size: + # how much data we evacuated last time + cell 0 +_gc_min_alloc: + # minimum possible allocation + cell 0 # tunable constant +_gc_grow_ratio: + # 256th's of the minimal amount of memory increment compared to the + # last time. New minimal amount is compared as: + # (ratio * last size) >> 8 + cell 0 # tunable constant +_gc_shrink_ratio: + # 256th's of the ratio of post-gc still-free to-space that should be considered for discarding + cell 0 + +_gc_region_start: + # in GC, this region is being evacuated and will eventually disappear + cell 0 +_gc_region_end: + # end of the disappear region + cell 0 + +_gc_backup_thunk: + # backup of %rsi so that we can use the register for other nonsense + cell 0 +_gc_backup_cont: + # backup of %rbp for same reason + cell 0 + +.section .text + +.macro needs_alloc amount + mov %rsp, %rax + sub _write_region_start, %rax + cmp \amount, %rax + jb _uskel_gc +.endm + +_uskel_alloc: + mov %rsi, %r15 # %rsi is the return address; back it up + + # calculate the desired size to %r14 + mov _gc_min_alloc, %r14 + #add _gc_region_end, %r14 + #sub _gc_region_start, %r14 + + # check if the desired size isn't greater because of the last gc use + mov _gc_last_size, %rax + mulq _gc_grow_ratio + shr $8, %rax + add _gc_min_alloc, %rax + cmp %r14, %rax + cmova %rax, %r14 + + # check if we don't need even more space because we need to evacuate stuff + mov _gc_region_end, %rax + sub %rsp, %rax # trick -- if we counted from gc region start, allocated memory could never shrink + cmp %r14, %rax + cmova %rax, %r14 + + and $0xfffffffffffffff8, %r14 #align + + alloc_goes_mmap: + mov $9, %rax # mmap + mov $0, %rdi # addr = NULL + mov %r14, %rsi # len = %r14 + mov $0b11, %rdx # prot = PROT_READ 0b1 | PROT_WRITE 0b10 + mov $0x22, %r10 # flags = MAP_PRIVATE 0x2 | MAP_ANONYMOUS 0x20 + mov $-1, %r8 # fd = -1 + mov $0, %r9 # off = 0 + syscall + + # store the results + mov %rax, _write_region_start + add %r14, %rax + mov %rax, _write_region_end + mov %rax, %rsp # initialize writing into the new region + + jmp *%r15 + +_uskel_gc_init: + mov %rsi, %r13 + movq $0x100000, _gc_min_alloc # must be higher than 2x the biggest thunk possible + movq $0x180, _gc_grow_ratio + movq $0x40, _gc_shrink_ratio + mov $0, %rsp # fake original rsp for first alloc run + mov $_uskel_gc_init_cont, %rsi + jmp _uskel_alloc + _uskel_gc_init_cont: + mov _write_region_start, %rax + mov %rax, _gc_trigger + jmp *%r13 + +_uskel_gc: + # save what we did before ending up here + mov %rbp, _gc_backup_thunk + mov %rsi, _gc_backup_cont + + # first we need a new memory area + mov _write_region_start, %rbx + mov _write_region_end, %rcx + mov %rbx, _gc_region_start + mov %rcx, _gc_region_end + mov $_uskel_gc_evacuate, %rsi + jmp _uskel_alloc + _uskel_gc_evacuate: + + # point the writer to the new memory area + mov _write_region_end, %rsp + mov %rsp, %r8 # % r8 is the "last thing that was scavenged" + + # start by evacuating the thunk and cont + mov _gc_backup_thunk, %rbp + mov $_uskel_gc_evacuate_cont_thunk, %rsi + jmp _gc_evacuate + _uskel_gc_evacuate_cont_thunk: + mov %rbp, _gc_backup_thunk + + mov _gc_backup_cont, %rbp + mov $_uskel_gc_evacuate_cont_cont, %rsi + jmp _gc_evacuate + _uskel_gc_evacuate_cont_cont: + mov %rbp, _gc_backup_cont + + # scavenge everything + _uskel_gc_scavenge: + # start at what we wrote last + mov %rsp, %rbp # rbp is the iterator (conveniently) + mov %rsp, %r9 # % r9 stores where we started with this evacuate round + + # if the thing is already scavenged, we didn't write anything, mark done. + cmp %rbp, %r8 + jbe _uskel_gc_scavenge_end + + _uskel_gc_scavenge1: + # if all ok, scavenge one thing (moving %rbp) and recheck + mov (%rbp), %rax + jmp *-020(%rax) # scavenge position in infotable + _gc_scavenge_ret: + cmp %rbp, %r8 + ja _uskel_gc_scavenge1 + + # everything above r9 is now scavenged, continue with next round + mov %r9, %r8 # we started at r9, so that is now "done" + jmp _uskel_gc_scavenge + + _uskel_gc_scavenge_end: + # deallocate the old memory region + mov $11, %rax # munmap + mov _gc_region_end, %rsi + mov _gc_region_start, %rdi # addr = gc start + sub %rdi, %rsi # len = gc end - gc start + syscall + + # recalculate the gc trigger point + mov %rsp, %rax + sub _write_region_start, %rax + mulq _gc_shrink_ratio + shr $8, %rax + add _write_region_start, %rax + mov %rax, _gc_trigger + + # save how much data we actually had at this point + mov _write_region_end, %rax + sub %rsp, %rax + mov %rax, _gc_last_size + + # restore what we were doing + mov _gc_backup_thunk, %rbp + mov _gc_backup_cont, %rsi + enter_rbp # for simplicity just restart the thunk + +_gc_evacuate: + # check if we are really out of the target region + cmp _write_region_start, %rbp + jb _gc_evacuate_go + cmp _write_region_end, %rbp + jae _gc_evacuate_go + _gc_evacuate_skip: + # if not, let's just jump to cont and leave %rbp as result + jmp *%rsi + _gc_evacuate_go: + # if we should evacuate, jump to the evac routine + mov %rbp, %r10 + mov (%rbp), %rax + jmp *-030(%rax) + _gc_evacuate_ret: + # install the indirection + movq $IND_code, 000(%r10) + mov %rbp, 010(%r10) + jmp *%rsi + + +.endif #_gc_s_file diff --git a/include/intops.s b/include/intops.s index dbadb37..08b3300 100644 --- a/include/intops.s +++ b/include/intops.s @@ -5,13 +5,13 @@ _intops_s_file: .include "include/primops.s" .primop2 plus - mov 010(%rsi), %rax # arg 2 + mov 010(%rsi), %rax # arg 2 mov 020(%rbp), %rsi # location of arg1 add 010(%rsi), %rax # arg 1 primop2_ret_int %rax .primop2 mul - mov 010(%rsi), %rax # arg 2 + mov 010(%rsi), %rax # arg 2 mov 020(%rbp), %rsi # location of arg1 mulq 010(%rsi) # arg 1 (goes to %rax and %rdx) primop2_ret_int %rax @@ -19,7 +19,7 @@ _intops_s_file: .primop2 sub mov 020(%rbp), %rdi # location of arg1 mov 010(%rdx), %rax # arg 1 - sub 010(%rsi), %rax # arg 2 + sub 010(%rsi), %rax # arg 2 primop2_ret_int %rax .endif # _intops_s_file diff --git a/include/io.s b/include/io.s index 205f144..29a63c9 100644 --- a/include/io.s +++ b/include/io.s @@ -4,11 +4,13 @@ _io_s_file: # | int | -> cont .thunkcode print + needs_alloc $040 thunkto %rsi, $print_fini, $2, %rbp, %rsi enter 020(%rbp) # arg -> | ret | cont | .thunkcode print_fini + needs_alloc $0110 #64 bit characters + 8 backup mov 010(%rsi), %rax # make a string @@ -24,7 +26,7 @@ _io_s_file: shr $1, %rax jnz print_fini_loop - mov $0, %rdi #stdin + mov $1, %rdi #stdout mov %rsp, %rdx sub %r15, %rdx #size mov %r15, %rsi #buf diff --git a/include/listops.s b/include/listops.s index 447c125..0ee39ad 100644 --- a/include/listops.s +++ b/include/listops.s @@ -6,6 +6,8 @@ _listops_s_file: # | n | list | -> cont .primop2 list_int_index + needs_alloc $060 + mov 010(%rsi), %rdx # the list constructor id, must be 1 cmp $1, %rdx jne list_int_index_not_found diff --git a/include/main_exit.s b/include/main_exit.s index 1a38e5a..5ce6c76 100644 --- a/include/main_exit.s +++ b/include/main_exit.s @@ -1,13 +1,10 @@ .ifndef _main_exit_s_file _main_exit_s_file: -# exitcode -> | cont (unused, should be 0) | +# exitcode -> || .thunkcode main_exit - mov 010(%rsi), %rdi # result goes to syscall exitcode + mov 010(%rsi), %rdi # result INT goes to syscall exitcode mov $60, %rax # exit=60 syscall # exit %rdi -# TODO this is a "case" kind of thunk so it's quite likely that it really -# doesn't need the continuation. - .endif # _main_exit_s_file diff --git a/include/primops.s b/include/primops.s index f532144..117bcca 100644 --- a/include/primops.s +++ b/include/primops.s @@ -9,6 +9,7 @@ _primops_s_file: .macro .primop1 name # | arg1 | -> cont .thunkcode \name + needs_alloc $040 # push a thunk for collecting the first arg and set it as continuation thunkto %rsi, $\name\()_fini, $2, %rbp, %rsi enter 020(%rbp) # evaluate arg1 @@ -39,6 +40,7 @@ _primops_s_file: .macro .primop2 name # | arg1 | arg2 | -> cont .thunkcode \name + needs_alloc $050 # push a thunk for collecting the first arg and set it as continuation thunkto %rsi, $\name\()_step1, $3, 030(%rbp), %rbp, %rsi enter 020(%rbp) # evaluate arg1 diff --git a/include/uskel.s b/include/uskel.s index 34ee63a..0a21217 100644 --- a/include/uskel.s +++ b/include/uskel.s @@ -9,46 +9,34 @@ _start: .include "include/macros.s" .section .bss -_memory_state: - cell 0 # bottom of allocation (grows down) - cell 0 # region start - cell 0 # region end - cell 0 # program entry rsp (aka the actual stack) +_unix_rsp: + # back-up of program entry rsp (aka the actual stack given by the + # actual OS; we might like to use it at some point, maybe) + cell 0 -.section .text +.include "include/gc.s" -_uskel_alloc_basic_mem: - mov $0x100000, %r15 # desired size - - mov $9, %rax # mmap - mov $0, %rdi # addr = NULL - mov %r15, %rsi # len = %rcx - mov $0b11, %rdx # prot = PROT_READ 0b1 | PROT_WRITE 0b10 - mov $0x22, %r10 # flags = MAP_PRIVATE 0x2 | MAP_ANONYMOUS 0x20 - mov $-1, %r8 # fd = -1 - mov $0, %r9 # off = 0 - syscall - mov $_memory_state, %rdi - mov %rax, 010(%rdi) - add %r15, %rax - mov %rax, (%rdi) - mov %rax, 020(%rdi) - retq +.section .text _uskel_start: - call _uskel_alloc_basic_mem - # use the stack pointer for easy writing to the heap, - # but back it up to memory state - mov $_memory_state, %rdi - mov %rsp, 030(%rdi) - mov 0(%rdi), %rsp + # we use the stack pointer for easy writing to the heap; + # back it up to memory state just if we ever needed it again. + mov %rsp, _unix_rsp + + # allocate the initial chunk of memory + mov $_uskel_start_main, %rsi + jmp _uskel_gc_init + _uskel_start_main: # push a thunk for main pushq $0 pushq $main - mov $0, %rsi # set continuation to exit + # loop the continuation to itself (prevents gc trouble, should never be reached) + mov %rsp, %rsi enter %rsp # run the program # Q: are there gonna be functions that have both the argument AND the cont? - # A: No, stuff is either entered as return-continuation (takes res, cont has to be saved) or as forward call (takes cont) - # (needs validation) + # A: No, stuff is either entered as return-continuation (takes res, + # cont has to be saved) or as forward call (takes cont) + # + # (A needs validation) |
