-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathregalloc.rb
356 lines (307 loc) · 9.44 KB
/
regalloc.rb
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# RegisterAllocator
#
# We have two principal forms of register usage:
# - temporary intermediate calculations
# - as a "cache"
#
# These two are in conflict. We maintain a list of
# "possible" registers.
#
# We then receive a prioritized list of variables
# to cache. The priority is used to determine which
# register to evict first if we run out of free
# registers.
#
# Note that this register allocator is extremely
# simplistic and does not in any way try to take
# into account control flows. The client (Emitter /
# Compiler classes) is expected to ensure the evict_all
# method is called at appropriate places to prevent
# the cache content from being relied on in situations
# where it is invalid (e.g. consider an if ... else .. end
# where a register is put into the cache in the "if" arm,
# in which case relying on this value being present in
# the "else" arm is obviously broken).
#
# We also don't attempt any kind of reordering etc.
# to maximize the utility of the register allocation.
#
# This is, in other words, the bare minimum we can
# get away with to see some benefits.
#
# If no register is free when we want a temporary
# register:
#
# - We evict one of the cached variables.
# - If there are still no registers free, we raise an
# error for now - it means we're careless about number
# of registers somewhere. Naughty.
#
#
# SPECIAL CONSIDERATIONS
#
# * Assignment or other mutation:
#
# Anywhere where the register is mutated, it *must* be
# marked as dirty, and a suitable spill block must be
# passed in to write the content back to memory when
# evicting the register. Note that this also makes it
# paramount that registers are evicted in any places
# where writing the contents back is necessary.
#
# FIXME: We currently will write the content back
# even if we have passed the last place a variable
# is actually used.
#
# * Loops:
#
# Variables allocated outside of the loop must either
# be allowed to stay in the same register, or be treated as
# evicted. For now we'll just evict all registers at the
# start of a loop iteration to avoid dealing with the complexities.
#
# * Lambda etc.
#
# All variables must be evicted. But we treat lambdas as functions
# at lower level, so we get that "for free"
#
# * Aliasing
#
# If a "let" aliases an outer variable, we need to evict that variable
# on entry and exit
#
class RegisterAllocator
# Represents a value cached in a register. For each cached instance,
# we keep the register name itself as a Symbol, an (optional) block
# to call when evicting this cached value from the register, that the
# emitter will use to spill the register back to memory. This is set
# only if a cached value is "dirtied" (by e.g. an assignment)
#
# Cached registers can get evicted at any time. So if we want a
# temporary register that needs to stay "live", we need to treat it
# differently. If we have a value that is already in a register, we
# allow "locking" it into place by setting the "locked" attribute
# of the cache entry.
class Cache
# The register this cache entry is for
attr_accessor :reg
# The variable it is cached for
attr_accessor :var
# The block to call to spill this register if the register is dirty (nil if the register is not dirty)
attr_accessor :spill
# True if this cache item can not be evicted because the register is in use.
attr_accessor :locked
def initialize reg, var
@reg = reg.to_sym
@var = var.to_sym
end
# Spill the (presumed dirty) value in the register in question,
# and assume that the register is no longer dirty. @spill is presumed
# to contain a block / Proc / object responding to #call that will
# handle the spill.
def spill!
@spill.call if @spill
@spill = nil
end
end
def initialize
# This is the list of registers that are available to be allocated
@registers = [:edx,:ecx, :edi]
# Register reserved for "self"
@selfreg = :esi
# Caller saved
@caller_saved = [:edx, :ecx, :edi]
reset!
end
def reset!
# Initially, all registers start out as free.
@free_registers = @registers.dup
@allocated_registers ||= Set.new
@cached = {}
@by_reg = {} # Cache information, by register
@order = {}
@allocators = []
end
# Order to prioritise variables in for register access
def order f
@order = f || {}
end
# This is primarily for testing, by making the test set of registers
# independent of the "real"
def registers= reg
@registers = reg
@free_registers = @registers.dup
end
def free_registers
@free_registers.dup
end
attr_writer :caller_saved
def evict_by_cache cache
return if !cache
@cached.delete(cache.var)
r = cache.reg
debug_is_register?(r, "evict_by_cache")
cache.spill!
@by_reg.delete(r)
# FIXME: BUG workaround? (was on one line)
if r != @selfreg
@free_registers << r
end
r ? cache.var : nil
end
# Remove any of the variables specified from the set of cached
# variables, if necessary spilling dirty variables back to memory.
#
# FIXME: For now this overrides the "locked" status. I'm not sure
# if this is safe or not.
#
def evict vars
Array(vars).collect do |v|
evict_by_cache(@cached[v.to_sym])
end.compact
end
def evict_all
evict(@cached.keys)
end
def evict_caller_saved(will_push = false)
to_push = will_push ? [] : nil
@caller_saved.each do |r|
evict_by_cache(@by_reg[r])
if @allocated_registers.member?(r)
if !will_push
raise "Allocated via with_register when crossing call boundary: #{r.inspect}"
else
to_push << r
end
end
end
return to_push
end
# Mark this cached register as "dirty". That is, the variable has been
# updated. In any situation where we evict the variable from this register,
# we then *must* save the register back to memory.
def mark_dirty(reg, block)
r = @by_reg[reg.to_sym]
r.spill = block if r
end
def debug_is_register?(reg, where)
return if reg && reg.to_sym == @selfreg
raise "NOT A REGISTER: #{reg.inspect} in #{where}" if !reg || [email protected]?(reg.to_sym)
end
# Called to "cache" a variable in a register. If no register is
# available, the emitter will use its scratch register (e.g. %eax)
# instead
def cache_reg!(var)
var = var.to_sym
# Already cached?
if r = @cached[var]
return r.reg
end
if var == :self
free = @selfreg
elsif [email protected]?(var)
return nil
else
free = @free_registers.shift
end
if free
debug_is_register?(free, "cache_reg!")
c = Cache.new(free,var)
k = var.to_sym
@cached[k] = c
@by_reg[free] = c
else
# STDERR.puts "NO FREE REGISTER (consider evicting if more important var?)"
end
free
end
# Return the register allocated to this variable, or nil if none
# is registered
def cached_reg(var)
c = @cached[var.to_sym]
c ? c.reg : nil
end
# Mark this register as locked to the cache, preventing it from
# being arbitrarily evicted by #with_register
def lock_reg(reg)
c = @by_reg[reg.to_sym]
c.locked=true if c
c
end
# Low level
def free!(free)
@allocated_registers.delete(free)
debug_is_register?(free, "free!")
@free_registers << free
end
def alloc!(r)
@allocated_registers << r
@free_registers.delete(r)
end
# Allocate a temporary register. If specified, we try to allocate
# a specific register.
def with_register(required_reg = nil)
if required_reg
free = @free_registers.delete(required_reg)
else
free = @free_registers.shift
end
if !free
# If no register was immediately free, but one or more
# registers is in use as cache, see if we can evict one of
# them.
if @cached.empty?
#STDERR.puts "No cached registers?"
else
# Figure out which register to drop, based on order.
# (least frequently used variable evicted first)
r = @order.reverse
r.each do |v|
# @FIXME: Workaround for @bug below
if !free
c = @cached[v]
#STDERR.puts "c: #{c.inspect}"
if c
if !c.locked
# @FIXME
# @bug variable name matching method name
# in lambda triggers rewrite bug.
xreg = c.reg
evict(v)
free = xreg
@free_registers.delete(free)
# @FIXME @bug Break here appears to reset ebx incorrectly.
# break
end
end
end
end
end
end
if !free
# This really should not happen, unless we are
# very careless about #with_register blocks.
STDERR.puts "==="
STDERR.puts @cached.inspect
STDERR.puts "--"
STDERR.puts @free_registers.inspect
STDERR.puts @allocators.inspect
#raise "Register allocation FAILED"
1/0
end
debug_is_register?(free, "with_register")
# This is for debugging of the allocator - we store
# a backtrace of where in the compiler the allocation
# was attempted from in case we run out of registers
# (which we shouldn't)
@allocators ||= []
@allocators << caller
# Mark the register as allocated, to prevent it from
# being reused.
alloc!(free)
yield(free)
# ... and clean up afterwards:
@allocators.pop
free!(free)
end
end