1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
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
|