Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | (* Copyright (C) 2017 Matthew Fluet. |
2 | * Copyright (C) 1999-2007 Henry Cejtin, Matthew Fluet, Suresh | |
3 | * Jagannathan, and Stephen Weeks. | |
4 | * Copyright (C) 1997-2000 NEC Research Institute. | |
5 | * | |
6 | * MLton is released under a BSD-style license. | |
7 | * See the file MLton-LICENSE for details. | |
8 | *) | |
9 | ||
10 | functor AllocateRegisters (S: ALLOCATE_REGISTERS_STRUCTS): ALLOCATE_REGISTERS = | |
11 | struct | |
12 | ||
13 | open S | |
14 | ||
15 | structure R = Rssa | |
16 | ||
17 | local | |
18 | open Rssa | |
19 | in | |
20 | structure Func = Func | |
21 | structure Function = Function | |
22 | structure Kind = Kind | |
23 | structure Label = Label | |
24 | structure Type = Type | |
25 | structure Var = Var | |
26 | end | |
27 | ||
28 | local | |
29 | open Machine | |
30 | in | |
31 | structure CType = CType | |
32 | structure Operand = Operand | |
33 | structure Register = Register | |
34 | structure Runtime = Runtime | |
35 | structure StackOffset = StackOffset | |
36 | end | |
37 | ||
38 | structure Live = Live (Rssa) | |
39 | ||
40 | structure Allocation: | |
41 | sig | |
42 | structure Stack: | |
43 | sig | |
44 | type t | |
45 | ||
46 | val get: t * Type.t -> t * {offset: Bytes.t} | |
47 | val layout: t -> Layout.t | |
48 | val new: StackOffset.t list -> t | |
49 | val size: t -> Bytes.t | |
50 | end | |
51 | ||
52 | type t | |
53 | ||
54 | val getRegister: t * Type.t -> Register.t | |
55 | val getStack: t * Type.t -> {offset: Bytes.t} | |
56 | val layout: t -> Layout.t | |
57 | val new: StackOffset.t list * Register.t list -> t | |
58 | val stack: t -> Stack.t | |
59 | val stackSize: t -> Bytes.t | |
60 | end = | |
61 | struct | |
62 | structure Stack = | |
63 | struct | |
64 | (* Keep a list of allocated slots sorted in increasing order of offset. | |
65 | *) | |
66 | datatype t = T of {offset: Bytes.t, size: Bytes.t} list | |
67 | ||
68 | fun layout (T alloc) = | |
69 | List.layout (fn {offset, size} => | |
70 | Layout.record [("offset", Bytes.layout offset), | |
71 | ("size", Bytes.layout size)]) | |
72 | alloc | |
73 | ||
74 | fun size (T alloc) = | |
75 | case alloc of | |
76 | [] => Bytes.zero | |
77 | | _ => let | |
78 | val {offset, size} = List.last alloc | |
79 | in | |
80 | Bytes.+ (offset, size) | |
81 | end | |
82 | ||
83 | fun new (alloc): t = | |
84 | let | |
85 | val a = | |
86 | Array.fromListMap (alloc, fn StackOffset.T {offset, ty} => | |
87 | {offset = offset, | |
88 | size = Type.bytes ty}) | |
89 | val () = | |
90 | QuickSort.sortArray | |
91 | (a, fn (r, r') => Bytes.<= (#offset r, #offset r')) | |
92 | fun loop (alloc, ac) = | |
93 | case alloc of | |
94 | [] => List.rev ac | |
95 | | [a] => List.rev (a::ac) | |
96 | | (a1 as {offset = offset1, size = size1})::(a2 as {offset = offset2, size = size2})::alloc => | |
97 | if Bytes.equals (Bytes.+ (offset1, size1), offset2) | |
98 | then loop ({offset = offset1, size = Bytes.+ (size1, size2)}::alloc, ac) | |
99 | else loop (a2::alloc, a1::ac) | |
100 | in | |
101 | T (loop (Array.toList a, [])) | |
102 | end | |
103 | ||
104 | fun get (T alloc, ty) = | |
105 | let | |
106 | val slotSize = Type.bytes ty | |
107 | fun loop (alloc, a as {offset, size}, ac) = | |
108 | let | |
109 | val prevEnd = Bytes.+ (offset, size) | |
110 | val begin = Type.align (ty, prevEnd) | |
111 | fun coalesce () = | |
112 | if Bytes.equals (prevEnd, begin) | |
113 | then ({offset = offset, size = Bytes.+ (size, slotSize)}, ac) | |
114 | else ({offset = begin, size = slotSize}, a :: ac) | |
115 | in | |
116 | case alloc of | |
117 | [] => | |
118 | let | |
119 | val (a, ac) = coalesce () | |
120 | in | |
121 | (T (rev (a :: ac)), {offset = begin}) | |
122 | end | |
123 | | (a' as {offset, size}) :: alloc => | |
124 | if Bytes.> (Bytes.+ (begin, slotSize), offset) | |
125 | then loop (alloc, a', | |
126 | if Bytes.isZero offset andalso Bytes.isZero size | |
127 | then ac | |
128 | else a :: ac) | |
129 | else let | |
130 | val (a'' as {offset = o', size = s'}, ac) = | |
131 | coalesce () | |
132 | val alloc = | |
133 | List.appendRev | |
134 | (ac, | |
135 | if Bytes.equals (Bytes.+ (o', s'), offset) | |
136 | then {offset = o', size = Bytes.+ (size, s')} :: alloc | |
137 | else a'' :: a' :: alloc) | |
138 | in | |
139 | (T alloc, {offset = begin}) | |
140 | end | |
141 | end | |
142 | in | |
143 | loop (alloc, {offset = Bytes.zero, size = Bytes.zero}, []) | |
144 | end | |
145 | val get = | |
146 | Trace.trace2 | |
147 | ("AllocateRegisters.Allocation.Stack.get", | |
148 | layout, Type.layout, | |
149 | Layout.tuple2 (layout, fn {offset} => | |
150 | Layout.record [("offset", Bytes.layout offset)])) | |
151 | get | |
152 | end | |
153 | structure Registers = | |
154 | struct | |
155 | (* A register allocation keeps track of the registers that have | |
156 | * already been allocated, for each runtime type. The reason that | |
157 | * we associate them with runtime types rather than Rssa types is | |
158 | * that the register indices that the codegens use are based on | |
159 | * runtime types. | |
160 | *) | |
161 | datatype t = T of CType.t -> {alloc: Register.t list, | |
162 | next: int} ref | |
163 | ||
164 | fun layout (T f) = | |
165 | List.layout | |
166 | (fn t => | |
167 | let | |
168 | val {alloc, next} = ! (f t) | |
169 | in | |
170 | Layout.record [("ty", CType.layout t), | |
171 | ("next", Int.layout next), | |
172 | ("alloc", List.layout Register.layout alloc)] | |
173 | end) | |
174 | CType.all | |
175 | ||
176 | fun compress {next, alloc} = | |
177 | let | |
178 | fun loop (next, alloc) = | |
179 | let | |
180 | fun done () = {alloc = alloc, | |
181 | next = next} | |
182 | in | |
183 | case alloc of | |
184 | [] => done () | |
185 | | r :: alloc => | |
186 | if next = Register.index r | |
187 | then loop (next + 1, alloc) | |
188 | else done () | |
189 | end | |
190 | in | |
191 | loop (next, alloc) | |
192 | end | |
193 | ||
194 | fun new (rs: Register.t list): t = | |
195 | let | |
196 | fun sameType (r, r') = | |
197 | CType.equals | |
198 | (Type.toCType (Register.ty r), | |
199 | Type.toCType (Register.ty r')) | |
200 | val rss = List.equivalence (rs, sameType) | |
201 | in | |
202 | T (CType.memo | |
203 | (fn t => | |
204 | case List.peek (rss, fn rs => | |
205 | case rs of | |
206 | [] => false | |
207 | | r :: _ => | |
208 | CType.equals | |
209 | (t, Type.toCType (Register.ty r))) of | |
210 | NONE => ref {alloc = [], next = 0} | |
211 | | SOME rs => | |
212 | ref | |
213 | (compress | |
214 | {next = 0, | |
215 | alloc = | |
216 | QuickSort.sortList | |
217 | (rs, fn (r, r') => | |
218 | Register.index r <= Register.index r')}))) | |
219 | end | |
220 | ||
221 | fun get (T f, ty: Type.t) = | |
222 | let | |
223 | val t = Type.toCType ty | |
224 | val r = f t | |
225 | val {alloc, next} = !r | |
226 | val reg = Register.new (ty, SOME next) | |
227 | val _ = | |
228 | r := compress {alloc = alloc, | |
229 | next = next + 1} | |
230 | in | |
231 | reg | |
232 | end | |
233 | end | |
234 | ||
235 | datatype t = T of {registers: Registers.t, | |
236 | stack: Stack.t ref} | |
237 | ||
238 | local | |
239 | fun make s (T x) = s x | |
240 | in | |
241 | val stack = ! o (make #stack) | |
242 | val stackSize = Stack.size o stack | |
243 | end | |
244 | ||
245 | fun layout (T {registers, stack}) = | |
246 | Layout.record | |
247 | [("stack", Stack.layout (!stack)), | |
248 | ("registers", Registers.layout registers)] | |
249 | ||
250 | fun getStack (T {stack, ...}, ty) = | |
251 | let | |
252 | val (s, offset) = Stack.get (!stack, ty) | |
253 | val _ = stack := s | |
254 | in | |
255 | offset | |
256 | end | |
257 | ||
258 | fun getRegister (T {registers, ...}, ty) = | |
259 | Registers.get (registers, ty) | |
260 | ||
261 | fun new (stack, registers) = | |
262 | T {registers = Registers.new registers, | |
263 | stack = ref (Stack.new stack)} | |
264 | end | |
265 | ||
266 | structure Info = | |
267 | struct | |
268 | type t = {live: Operand.t vector, | |
269 | liveNoFormals: Operand.t vector, | |
270 | size: Bytes.t} | |
271 | ||
272 | fun layout ({live, liveNoFormals, size, ...}: t) = | |
273 | Layout.record | |
274 | [("live", Vector.layout Operand.layout live), | |
275 | ("liveNoFormals", Vector.layout Operand.layout liveNoFormals), | |
276 | ("size", Bytes.layout size)] | |
277 | end | |
278 | ||
279 | (* ------------------------------------------------- *) | |
280 | (* allocate *) | |
281 | (* ------------------------------------------------- *) | |
282 | ||
283 | fun allocate {formalsStackOffsets, | |
284 | function = f: Rssa.Function.t, | |
285 | varInfo: Var.t -> {operand: Machine.Operand.t option ref option, | |
286 | ty: Type.t}} = | |
287 | let | |
288 | fun diagnostics f = | |
289 | Control.diagnostics | |
290 | (fn display => | |
291 | let | |
292 | open Layout | |
293 | fun diagVar (x: Var.t): unit = | |
294 | display (seq | |
295 | [Var.layout x, str " ", | |
296 | Option.layout | |
297 | (fn r => Option.layout Operand.layout (!r)) | |
298 | (#operand (varInfo x))]) | |
299 | fun diagStatement (s: R.Statement.t): unit = | |
300 | R.Statement.foreachDef (s, diagVar o #1) | |
301 | in | |
302 | f (display, diagVar, diagStatement) | |
303 | end) | |
304 | val _ = | |
305 | Control.diagnostic (fn () => | |
306 | let open Layout | |
307 | in seq [str "Function allocs for ", | |
308 | Func.layout (Function.name f)] | |
309 | end) | |
310 | val {labelLive, remLabelLive} = | |
311 | Live.live (f, {shouldConsider = isSome o #operand o varInfo}) | |
312 | val {args, blocks, name, ...} = Function.dest f | |
313 | (* | |
314 | * Decide which variables will live in stack slots and which | |
315 | * will live in registers. | |
316 | * Initially, | |
317 | * - all formals are put in stack slots | |
318 | * - everything else is put in a register. | |
319 | * Variables get moved to the stack if they are | |
320 | * - live at the beginning of a Cont block; such variables are | |
321 | * live while the frame is suspended during a non-tail call | |
322 | * and must be stack allocated to be traced during a GC | |
323 | * - live at the beginning of a CReturn block that mayGC; such | |
324 | * variables are live while the frame is suspended during a | |
325 | * C call and must be stack allocated to be traced during | |
326 | * the potential GC | |
327 | * Both of the above are indiced by Kind.frameStyle kind = | |
328 | * Kind.OffsetsAndSize | |
329 | *) | |
330 | datatype place = Stack | Register | |
331 | val {get = place: Var.t -> place ref, rem = removePlace, ...} = | |
332 | Property.get (Var.plist, Property.initFun (fn _ => ref Register)) | |
333 | (* !hasHandler = true iff handlers are installed in this function. *) | |
334 | val hasHandler: bool ref = ref false | |
335 | fun forceStack (x: Var.t): unit = place x := Stack | |
336 | val _ = Vector.foreach (args, forceStack o #1) | |
337 | val _ = | |
338 | Vector.foreach | |
339 | (blocks, | |
340 | fn R.Block.T {kind, label, statements, ...} => | |
341 | let | |
342 | val {beginNoFormals, ...} = labelLive label | |
343 | val _ = | |
344 | case Kind.frameStyle kind of | |
345 | Kind.None => () | |
346 | | Kind.OffsetsAndSize => | |
347 | Vector.foreach (beginNoFormals, forceStack) | |
348 | | Kind.SizeOnly => () | |
349 | val _ = | |
350 | if not (!hasHandler) | |
351 | andalso (Vector.exists | |
352 | (statements, fn s => | |
353 | let | |
354 | datatype z = datatype R.Statement.t | |
355 | in | |
356 | case s of | |
357 | SetHandler _ => true | |
358 | | SetExnStackLocal => true | |
359 | | SetExnStackSlot => true | |
360 | | SetSlotExnStack => true | |
361 | | _ => false | |
362 | end)) | |
363 | then hasHandler := true | |
364 | else () | |
365 | in | |
366 | () | |
367 | end) | |
368 | fun allocateVar (x: Var.t, a: Allocation.t): unit = | |
369 | let | |
370 | val {operand, ty} = varInfo x | |
371 | in | |
372 | if isSome operand | |
373 | then let | |
374 | val oper = | |
375 | case ! (place x) of | |
376 | Stack => | |
377 | let | |
378 | val {offset} = Allocation.getStack (a, ty) | |
379 | in | |
380 | Operand.StackOffset | |
381 | (StackOffset.T {offset = offset, ty = ty}) | |
382 | end | |
383 | | Register => | |
384 | Operand.Register | |
385 | (Allocation.getRegister (a, ty)) | |
386 | val () = removePlace x | |
387 | val _ = | |
388 | case operand of | |
389 | NONE => () | |
390 | | SOME r => r := SOME oper | |
391 | in | |
392 | () | |
393 | end | |
394 | else () | |
395 | end | |
396 | val allocateVar = | |
397 | Trace.trace2 | |
398 | ("AllocateRegisters.allocateVar", Var.layout, Allocation.layout, Unit.layout) | |
399 | allocateVar | |
400 | (* Set the stack slots for the formals. | |
401 | * Also, create a stack allocation that includes all formals; if | |
402 | * link and handler stack slots are required, then they will be | |
403 | * allocated against this stack. | |
404 | *) | |
405 | val stack = | |
406 | Allocation.Stack.new | |
407 | (Vector.foldr2 | |
408 | (args, formalsStackOffsets args, [], | |
409 | fn ((x, _), so, stack) => | |
410 | (valOf (#operand (varInfo x)) := SOME (Operand.StackOffset so) | |
411 | ; so :: stack))) | |
412 | (* Allocate stack slots for the link and handler, if necessary. *) | |
413 | val handlerLinkOffset = | |
414 | if !hasHandler | |
415 | then | |
416 | let | |
417 | (* Choose fixed and permanently allocated stack | |
418 | * slots that do not conflict with formals. | |
419 | *) | |
420 | val (stack, {offset = handler, ...}) = | |
421 | Allocation.Stack.get (stack, Type.label (Label.newNoname ())) | |
422 | val (_, {offset = link, ...}) = | |
423 | Allocation.Stack.get (stack, Type.exnStack ()) | |
424 | in | |
425 | SOME {handler = handler, link = link} | |
426 | end | |
427 | else NONE | |
428 | fun getOperands (xs: Var.t vector): Operand.t vector = | |
429 | Vector.map (xs, fn x => valOf (! (valOf (#operand (varInfo x))))) | |
430 | val getOperands = | |
431 | Trace.trace | |
432 | ("AllocateRegisters.getOperands", | |
433 | Vector.layout Var.layout, Vector.layout Operand.layout) | |
434 | getOperands | |
435 | val {get = labelInfo: R.Label.t -> Info.t, set = setLabelInfo, ...} = | |
436 | Property.getSetOnce (R.Label.plist, | |
437 | Property.initRaise ("labelInfo", R.Label.layout)) | |
438 | val setLabelInfo = | |
439 | Trace.trace2 | |
440 | ("AllocateRegisters.setLabelInfo", | |
441 | R.Label.layout, Info.layout, Unit.layout) | |
442 | setLabelInfo | |
443 | (* Do a DFS of the control-flow graph. *) | |
444 | val () = | |
445 | Function.dfs | |
446 | (f, fn R.Block.T {args, label, kind, statements, transfer, ...} => | |
447 | let | |
448 | val {begin, beginNoFormals, handler = handlerLive, | |
449 | link = linkLive} = labelLive label | |
450 | val () = remLabelLive label | |
451 | fun addHS (ops: Operand.t vector): Operand.t vector = | |
452 | case handlerLinkOffset of | |
453 | NONE => ops | |
454 | | SOME {handler, link} => | |
455 | let | |
456 | val extra = [] | |
457 | val extra = | |
458 | case handlerLive of | |
459 | NONE => extra | |
460 | | SOME h => | |
461 | Operand.stackOffset {offset = handler, | |
462 | ty = Type.label h} | |
463 | :: extra | |
464 | val extra = | |
465 | if linkLive | |
466 | then | |
467 | Operand.stackOffset {offset = link, | |
468 | ty = Type.exnStack ()} | |
469 | :: extra | |
470 | else extra | |
471 | in | |
472 | Vector.concat [Vector.fromList extra, ops] | |
473 | end | |
474 | val liveNoFormals = getOperands beginNoFormals | |
475 | val (stackInit, registersInit) = | |
476 | Vector.fold | |
477 | (liveNoFormals, ([],[]), fn (oper, (stack, registers)) => | |
478 | case oper of | |
479 | Operand.StackOffset s => (s::stack, registers) | |
480 | | Operand.Register r => (stack, r::registers) | |
481 | | _ => (stack, registers)) | |
482 | val stackInit = | |
483 | case handlerLinkOffset of | |
484 | NONE => stackInit | |
485 | | SOME {handler, link} => | |
486 | StackOffset.T {offset = handler, | |
487 | ty = Type.label (Label.newNoname ())} | |
488 | :: StackOffset.T {offset = link, | |
489 | ty = Type.exnStack ()} | |
490 | :: stackInit | |
491 | val a = Allocation.new (stackInit, registersInit) | |
492 | val size = | |
493 | case kind of | |
494 | Kind.Handler => | |
495 | (case handlerLinkOffset of | |
496 | NONE => Error.bug "AllocateRegisters.allocate: Handler with no handler offset" | |
497 | | SOME {handler, ...} => | |
498 | Bytes.+ (Runtime.labelSize (), handler)) | |
499 | | _ => | |
500 | let | |
501 | val size = | |
502 | Bytes.+ | |
503 | (Runtime.labelSize (), | |
504 | Bytes.alignWord32 (Allocation.stackSize a)) | |
505 | in | |
506 | case !Control.align of | |
507 | Control.Align4 => size | |
508 | | Control.Align8 => Bytes.alignWord64 size | |
509 | end | |
510 | val _ = | |
511 | if Bytes.isWord32Aligned size | |
512 | then () | |
513 | else Error.bug (concat ["AllocateRegisters.allocate: ", | |
514 | "bad size ", | |
515 | Bytes.toString size, | |
516 | " in ", Label.toString label]) | |
517 | val _ = Vector.foreach (args, fn (x, _) => allocateVar (x, a)) | |
518 | (* Must compute live after allocateVar'ing the args, since that | |
519 | * sets the operands for the args. | |
520 | *) | |
521 | val live = getOperands begin | |
522 | fun one (var, _) = allocateVar (var, a) | |
523 | val _ = | |
524 | Vector.foreach (statements, fn statement => | |
525 | R.Statement.foreachDef (statement, one)) | |
526 | val _ = R.Transfer.foreachDef (transfer, one) | |
527 | val _ = | |
528 | setLabelInfo (label, {live = addHS live, | |
529 | liveNoFormals = addHS liveNoFormals, | |
530 | size = size}) | |
531 | in | |
532 | fn () => () | |
533 | end) | |
534 | val () = | |
535 | diagnostics | |
536 | (fn (display, diagVar, diagStatement) => | |
537 | let | |
538 | open Layout | |
539 | val _ = | |
540 | display (seq [str "function ", Func.layout name, | |
541 | str " handlerLinkOffset ", | |
542 | Option.layout | |
543 | (fn {handler, link} => | |
544 | record [("handler", Bytes.layout handler), | |
545 | ("link", Bytes.layout link)]) | |
546 | handlerLinkOffset]) | |
547 | val _ = Vector.foreach (args, diagVar o #1) | |
548 | val _ = | |
549 | Vector.foreach | |
550 | (blocks, fn R.Block.T {label, args, statements, ...} => | |
551 | let | |
552 | val {live, ...} = labelInfo label | |
553 | val () = display (R.Label.layout label) | |
554 | val () = | |
555 | display | |
556 | (seq [str "live: ", Vector.layout Operand.layout live]) | |
557 | val () = Vector.foreach (args, diagVar o #1) | |
558 | val () = Vector.foreach (statements, diagStatement) | |
559 | in | |
560 | () | |
561 | end) | |
562 | in () | |
563 | end) | |
564 | in | |
565 | {handlerLinkOffset = handlerLinkOffset, | |
566 | labelInfo = labelInfo} | |
567 | end | |
568 | ||
569 | val allocate = | |
570 | Trace.trace | |
571 | ("AllocateRegisters.allocate", | |
572 | fn {function, ...} => Func.layout (Function.name function), | |
573 | Layout.ignore) | |
574 | allocate | |
575 | ||
576 | end |