(* A register allocator for MIPS. *) module RegAlloc (* registerAlloc takes a list of MIPS instructions, a set of registers that are live at the end of the code, three register numbers: 1) The lowest allocatable register (typically 2). 2) The highest caller-saves register. 3) The highest allocatable register (typically 25). and the number of already spilled variables. This should be 0 in the initial call unless some variables are forced to spill before register allocation. Registers up to (and including) the highest caller-saves register are assumed to be caller-saves. Those above are assumed to be callee-saves. registerAlloc returns: a modified instruction list where null moves have been removed, a set of the variables that are live at entry, plus a number indicating the highest used register number. The latter can be used for deciding which callee-saves registers need to be saved. Limitations: - Works for a single procedure body only. - Assumes all JALs eventually return to the next instruction and preserve callee-saves registers when doing so. - Does caller-saves preservation only by allocating variables that are live across procedure calls to callee-saves registers and variables not live across call preferably to caller-saves. - Can only remove null moves if they are implemented by ORI (rx,ry,"0"). Use the pseudo-instruction MOVE (rx,ry) for this. *) open Mips exception MyError of string exception Not_colourable of string let spilledVars : Set ref = ref (Set.empty) let rec destRegs (lst : Instruction list) : Set = match lst with | [] -> Set.empty | (i::ilist) -> Set.union (destReg i) (destRegs ilist) (* variables and registers that can be overwritten *) and destReg (i : Instruction) : Set = match i with | LA (rt,v) -> Set.singleton rt | LUI (rt,v) -> Set.singleton rt | ADD (rd,rs,rt) -> Set.singleton rd | ADDI (rd,rs,v) -> Set.singleton rd | SUB (rd,rs,rt) -> Set.singleton rd | MUL (rd,rs,rt) -> Set.singleton rd | DIV (rd,rs,rt) -> Set.singleton rd | AND (rd,rs,rt) -> Set.singleton rd | ANDI (rd,rs,v) -> Set.singleton rd | OR (rd,rs,rt) -> Set.singleton rd | ORI (rd,rs,v) -> Set.singleton rd | XOR (rd,rs,rt) -> Set.singleton rd | XORI (rd,rs,v) -> Set.singleton rd | SLL (rd,rt,v) -> Set.singleton rd | SRA (rd,rt,v) -> Set.singleton rd | SLT (rd,rs,rt) -> Set.singleton rd | SLTI (rd,rs,v) -> Set.singleton rd | JAL (lab,argRegs) -> Set.add (RN 31) (Set.ofList argRegs) | LW (rd,rs,v) -> Set.singleton rd | LB (rd,rs,v) -> Set.singleton rd | SYSCALL -> Set.singleton (RN 2) (* return value is in $2 *) | _ -> Set.empty (* variables and register that can be read by i *) let usedRegs (i : Instruction) : Set = match i with | ADD (rd,rs,rt) -> Set.ofList [rs;rt] | ADDI (rd,rs,v) -> Set.singleton rs | SUB (rd,rs,rt) -> Set.ofList [rs;rt] | MUL (rd,rs,rt) -> Set.ofList [rs;rt] | DIV (rd,rs,rt) -> Set.ofList [rs;rt] | AND (rd,rs,rt) -> Set.ofList [rs;rt] | ANDI (rd,rs,v) -> Set.singleton rs | OR (rd,rs,rt) -> Set.ofList [rs;rt] | ORI (rd,rs,v) -> Set.singleton rs | XOR (rd,rs,rt) -> Set.ofList [rs;rt] | XORI (rd,rs,v) -> Set.singleton rs | SLL (rd,rt,v) -> Set.singleton rt | SRA (rd,rt,v) -> Set.singleton rt | SLT (rd,rs,rt) -> Set.ofList [rs;rt] | SLTI (rd,rs,v) -> Set.singleton rs | BEQ (rs,rt,v) -> Set.ofList [rs;rt] | BNE (rs,rt,v) -> Set.ofList [rs;rt] | BGEZ (rs,v) -> Set.singleton rs | J lab -> Set.empty | JAL (lab,argRegs) -> Set.ofList argRegs (* argRegs are argument registers *) | JR (r,resRegs) -> Set.ofList (r::resRegs) (* r is jump register, resRegs are registers required to be live *) | LW (rd,rs,v) -> Set.singleton rs | SW (rd,rs,v) -> Set.ofList [rs;rd] | LB (rd,rs,v) -> Set.singleton rs | SB (rd,rs,v) -> Set.ofList [rs;rd] | SYSCALL -> Set.ofList [RN 2; RN 4; RN 5] (* $2 is control register and $4, $5 are arguments *) | _ -> Set.empty let live_step ilist llist liveAtEnd = let rec scan (is : Instruction list) = match is with | [] -> [] | (i::is) -> let ls1 = scan is if List.isEmpty ls1 then [instruct i liveAtEnd] else (instruct i (List.head ls1)) :: ls1 (* live variables and registers *) and instruct (i : Instruction) (live : Set) : Set = match i with | BEQ (rs,rt,v) -> Set.union (Set.ofList [rs;rt]) (Set.union live (live_at v)) | BNE (rs,rt,v) -> Set.union (Set.ofList [rs;rt]) (Set.union live (live_at v)) | BGEZ (rs,v) -> Set.union (Set.singleton rs) (Set.union live (live_at v)) | J lab -> live_at lab | JR (r,resRegs) -> Set.ofList (r::resRegs) (* r is jump register, resRegs are registers required to be live *) | _ -> Set.union (usedRegs i) (Set.difference live (destReg i)) and live_at lab : Set = search ilist llist lab and search a1 a2 a3 : Set = match (a1, a2, a3) with | ([], [], lab) -> Set.empty | (LABEL k :: is, l::ls, lab) -> if k = lab then l else search is ls lab | (_::is, _::ls, lab) -> search is ls lab | (a, b, l) -> raise (MyError "should not happen in RegAlloc.live_step.search!") let res = scan ilist res let rec iterate_live ilist llist liveAtEnd = let llist1 = live_step ilist llist liveAtEnd if llist1 = llist then llist else iterate_live ilist llist1 liveAtEnd let rec init_list = function | [] -> [] | (i::is) -> Set.empty :: init_list is (* live_regs finds for each instruction those symbolic register names *) (* that are live at entry to this instruction *) let live_regs ilist liveAtEnd = iterate_live ilist (init_list ilist) liveAtEnd let rec regs lst (rs : Set) : Set = match lst with | [] -> rs | (l :: llist) -> Set.union l (regs llist rs) let numerical r = match r with | RN _ -> true | RS _ -> false let filterSymbolic rs = Set.filter (fun a -> not (numerical a)) rs let rec findRegs llist = filterSymbolic (regs llist Set.empty) (* conflicts ilist llist callerSaves r *) (* finds those variables that interferere with r *) (* in instructions ilist with live-out specified by llist *) (* callerSaves are the caller-saves registers *) let rec conflicts = function | ([], [], callerSaves, RN r) -> Set.remove (RN r) callerSaves (* all numerical interfere with all other caller-saves *) | ([], [], callerSaves, RS _) -> Set.empty | (ORI (rd,rs,0) :: ilist, l :: llist, callerSaves, r) -> if r=rd (* if destination *) then Set.union (Set.remove rs (Set.remove r l)) (* interfere with live except rs *) (conflicts (ilist, llist, callerSaves, r)) else if r=rs (* if source, no interference *) then conflicts (ilist, llist, callerSaves, r) else if Set.contains r l (* otherwise, live interfere with rd *) then Set.add rd (conflicts (ilist, llist, callerSaves, r)) else conflicts (ilist, llist, callerSaves, r) | (JAL (f,argRegs) :: ilist, l :: llist, callerSaves, r) -> if (Set.contains r l) (* live vars interfere with caller-saves regs *) then Set.union (Set.remove r callerSaves) (conflicts (ilist, llist, callerSaves, r)) else if Set.contains r callerSaves then Set.union (Set.remove r l) (conflicts (ilist, llist, callerSaves, r)) else conflicts (ilist, llist, callerSaves, r) | (i :: ilist, l :: llist, callerSaves, r) -> if (Set.contains r (destReg i)) (* destination register *) then Set.union (Set.remove r l) (* conflicts with other live vars *) (conflicts (ilist, llist, callerSaves, r)) else if Set.contains r l (* all live vars *) then Set.union (destReg i) (* conflict with destination *) (conflicts (ilist, llist, callerSaves, r)) else conflicts (ilist, llist, callerSaves, r) | _ -> raise (MyError "conflicts used at undefined instance") (* Interference graph is represented as a list of registers *) (* each paired with a list of the registers with which it conflicts *) let graph ilist llist callerSaves = let rs = Set.union callerSaves (findRegs llist) |> Set.toList List.zip rs (List.map (fun r -> conflicts (ilist, ((List.tail llist)@[Set.empty]), callerSaves, r)) rs) (* finds move-related registers *) let rec findMoves ilist llist = let rs = findRegs llist |> Set.toList List.zip rs (List.map (fun r -> findMoves1 r ilist) rs) and findMoves1 r = function | [] -> Set.empty | (ORI (rd,rs,0) :: ilist) -> Set.union ( if rd=r then Set.singleton rs elif rs=r then Set.singleton rd else Set.empty) (findMoves1 r ilist) | (i::ilist) -> findMoves1 r ilist (* sorts by number of conflicts, but with numeric registers last *) let be4 (a, ac) (b, bc) = match (a, b) with | (RN i, RN j) -> i <= j | (RN _, RS _) -> false | (RS _, RN _) -> true | (RS sa, RS sb) -> match (Set.contains sa (!spilledVars), Set.contains sb (!spilledVars)) with | (false, false) -> Set.count ac <= Set.count bc | (true , false) -> false | (false, true ) -> true | (true , true ) -> Set.count ac <= Set.count bc let rec sortByOrder = function | [] -> [] | (g : (reg * Set<'b>) list) -> let rec split = function | [] -> ([],[]) | (a::g) -> let (l, g1) = ascending a g [] let (g2,g3) = split g1 (rev2 l g3, g2) and ascending a g l = match g with | [] -> (a::l,[]) | (b::g1) -> if be4 a b then ascending b g1 (a::l) else (a::l,g) and rev2 g l2 = match g with | [] -> l2 | (a::l1) -> rev2 l1 (a::l2) let rec merge = function | ([], l2) -> l2 | (l1, []) -> l1 | (a::r1, b::r2) -> if be4 a b then a :: merge (r1, b::r2) else b :: merge (a::r1, r2) let (g1,g2) = split g if List.isEmpty g1 then g2 elif List.isEmpty g2 then g1 else merge (sortByOrder g1, sortByOrder g2) (* n-colour graph using Briggs' algorithm *) let rec colourGraph g rmin rmax moveRelated = select (simplify (sortByOrder g) []) (mList rmin rmax) moveRelated [] and simplify h l = match h with | [] -> l | (r,c) :: g -> simplify (sortByOrder (removeNode r g)) ((r,c)::l) and removeNode r = function | [] -> [] | ((r1,c)::g) -> (r1,Set.remove r c) :: removeNode r g and select rcl regs moveRelated sl = match rcl with | [] -> sl | ((r,c)::l) -> let rnum = if numerical r then r else let possible = NotIn c sl regs let related = lookUp2 r moveRelated let related2 = Set.map (fun r -> lookUp r sl) related let mPossible= Set.intersect possible related2 if Set.isEmpty possible then raise (Not_colourable (ppReg r)) elif Set.isEmpty mPossible then Set.minElement possible //hd possible else Set.minElement mPossible //hd mPossible select l regs moveRelated ((r,rnum)::sl) and NotIn rcs sl regs : Set = Set.fold (fun acc r -> Set.remove (lookUp r sl) acc) regs rcs and lookUp r = function | [] -> RN 0 | ((r1,n)::sl) -> if numerical r then r else if r=r1 then n else lookUp r sl and lookUp2 r = function | [] -> Set.empty | ((r1,ms)::sl) -> if r=r1 then ms else lookUp2 r sl and mList m n : Set = if m > n then Set.empty else Set.add (RN m) (mList (m+1) n) let rec filterNullMoves ilist allocs = match ilist with | [] -> [] | (ORI (rd,rs,0) :: ilist_tl) -> let rd1 = lookUp rd allocs let rs1 = lookUp rs allocs if rd1 = rs1 || rd1 = RN 0 then COMMENT ("\tori\t"+ ppReg rd+","+ ppReg rs+",0") :: filterNullMoves ilist_tl allocs else ORI (rd,rs,0) :: filterNullMoves ilist_tl allocs | (i :: ilist_tl) -> i :: filterNullMoves ilist_tl allocs and printList = function | [] -> "" | (r :: rs) -> r+" "+ printList rs let rec printGraph = function | [] -> [] | ((r,rs) :: g) -> [COMMENT ("interferes: "+r+" with "+printList rs)] @ printGraph g let renameReg allocs inst = let renTo inst1 = [inst1; COMMENT ("was:" + ppMips inst)] match inst with | LA (rt,l) -> renTo (LA (lookUp rt allocs, l)) | LUI (rt,v) -> renTo (LUI (lookUp rt allocs, v)) | ADD (rd,rs,rt) -> renTo (ADD (lookUp rd allocs, lookUp rs allocs, lookUp rt allocs)) | ADDI (rd,rs,v) -> renTo (ADDI (lookUp rd allocs, lookUp rs allocs, v)) | SUB (rd,rs,rt) -> renTo (SUB (lookUp rd allocs, lookUp rs allocs, lookUp rt allocs)) | MUL (rd,rs,rt) -> renTo (MUL (lookUp rd allocs, lookUp rs allocs, lookUp rt allocs)) | DIV (rd,rs,rt) -> renTo (DIV (lookUp rd allocs, lookUp rs allocs, lookUp rt allocs)) | AND (rd,rs,rt) -> renTo (AND (lookUp rd allocs, lookUp rs allocs, lookUp rt allocs)) | ANDI (rd,rs,v) -> renTo (ANDI (lookUp rd allocs, lookUp rs allocs, v)) | OR (rd,rs,rt) -> renTo (OR (lookUp rd allocs, lookUp rs allocs, lookUp rt allocs)) | ORI (rd,rs,v) -> renTo (ORI (lookUp rd allocs, lookUp rs allocs, v)) | XOR (rd,rs,rt) -> renTo (XOR (lookUp rd allocs, lookUp rs allocs, lookUp rt allocs)) | XORI (rd,rs,v) -> renTo (XORI (lookUp rd allocs, lookUp rs allocs, v)) | SLL (rd,rt,v) -> renTo (SLL (lookUp rd allocs, lookUp rt allocs, v)) | SRA (rd,rt,v) -> renTo (SRA (lookUp rd allocs, lookUp rt allocs, v)) | SLT (rd,rs,rt) -> renTo (SLT (lookUp rd allocs, lookUp rs allocs, lookUp rt allocs)) | SLTI (rd,rs,v) -> renTo (SLTI (lookUp rd allocs, lookUp rs allocs, v)) | BEQ (rs,rt,l) -> renTo (BEQ (lookUp rs allocs, lookUp rt allocs, l)) | BGEZ(rs,l) -> renTo (BGEZ(lookUp rs allocs, l)) | BNE (rs,rt,l) -> renTo (BNE (lookUp rs allocs, lookUp rt allocs, l)) | JAL (lab,argRegs) -> [JAL (lab, List.map (fun r -> lookUp r allocs) argRegs); COMMENT ("was:" + ppMips inst + ", " + String.concat " " (List.map ppReg argRegs))] | JR (r, resRegs) -> [JR (lookUp r allocs, List.map (fun r -> lookUp r allocs) resRegs); COMMENT ("was:" + ppMips inst + ", " + String.concat " " (List.map ppReg resRegs))] | LW (rd,rs,v) -> renTo (LW (lookUp rd allocs, lookUp rs allocs, v)) | SW (rd,rs,v) -> renTo (SW (lookUp rd allocs, lookUp rs allocs, v)) | LB (rd,rs,v) -> renTo (LB (lookUp rd allocs, lookUp rs allocs, v)) | SB (rd,rs,v) -> renTo (SB (lookUp rd allocs, lookUp rs allocs, v)) | _ -> [inst] let spill1 i r offset = let d = destReg i let u = usedRegs i let hdlst = if Set.contains r u then [Mips.LW (r, RN 29, offset)] else [] let tllst = if Set.contains r d then [Mips.SW (r, RN 29, offset)] else [] hdlst @ [i] @ tllst let rec spill ilist r offset = match ilist with | [] -> [] | (i::is) -> spill1 i r offset @ spill is r offset let rec maxreg lst m = match lst with | [] -> m | ((r,RN n)::rs) -> maxreg rs (if m < n then n else m) | ((_,RS _)::rs) -> raise (MyError "maxreg of non-numeric register") (* arguments: ilist is list of MIPS instructions liveAtEnd is a set of variables that are live at the end of ilist rmin is first allocable register (caller-saves) callerMax is highest caller-saves register rmax is highest allocable register spilled is number of registers spilled so far -- should be 0 initially *) let rec registerAlloc (ilist : Mips.Instruction list) (liveAtEnd : Set) (rmin : int) (callerMax : int) (rmax : int) (spilled : int) : (Mips.Instruction list * Set * int * int) = try let llist = live_regs ilist liveAtEnd let callerSaves = mList rmin callerMax let iGraph = graph ilist llist callerSaves let moveRelated = findMoves ilist llist let allocs = colourGraph iGraph rmin rmax moveRelated let deadRegs = Set.difference (filterSymbolic (destRegs ilist)) ( (List.map (fun (x,_) -> x) allocs) |> Set.ofList ) let allocs1 = allocs @ (List.map (fun r -> (r, RN 0)) (Set.toList deadRegs)) let ilist1 = filterNullMoves ilist allocs1 let ilist2 = List.concat (List.map (renameReg allocs1) ilist1) (ilist2, List.head llist, maxreg allocs 0, spilled) with | (Not_colourable sr) -> printfn "%s spilled\n" sr spilledVars := Set.add sr (!spilledVars) let offset = (4*spilled) let ilist' = spill ilist (RS sr) offset let ilist'' = [Mips.SW (RS sr, RN 29,offset)] @ ilist' @ (if Set.contains (RS sr) liveAtEnd then [Mips.LW (RS sr, RN 29, offset)] else []) registerAlloc ilist'' liveAtEnd rmin callerMax rmax (spilled + 1)