Skip to content

Commit cedbbfa

Browse files
committed
change transform_inline to be aware of checkpoints
To properly support osr across inlining the inlining pass needs to know if there is a checkpoint after the function call at the caller site. This patch refactors compute_inlining_order to not just return the order, but in fact an annotated graph that contains all the information for the inliner to go forward. At the core there is the `inlining_candidate` which contains all information to apply one inlining. The inlinings are performed recursively, all in one go. At the end a new version is returned which can be added to the first caller function. Currently inlinings are skipped when changes to the osr points would be needed.
1 parent 7528c96 commit cedbbfa

File tree

1 file changed

+109
-124
lines changed

1 file changed

+109
-124
lines changed

transform_inline.ml

Lines changed: 109 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,19 @@ type inlining_input = {
99
instrs : instructions;
1010
}
1111

12-
let as_inlining_input (func : afunction) (version : version) : inlining_input =
13-
{ formals = Analysis.as_var_list func.formals; instrs = version.instrs }
12+
let as_inlining_input (func : afunction) : inlining_input =
13+
{ formals = Analysis.as_var_list func.formals; instrs = (active_version func).instrs }
14+
15+
type inlining_candidate = {
16+
pos : pc;
17+
target : afunction;
18+
ret : variable;
19+
args : argument list;
20+
osr : osr_frame_map list option;
21+
next : inlining_site;
22+
}
23+
and inlining_site = inlining_candidate list
24+
1425

1526
(* FUNCTION INLINING *)
1627
(* Given a program, inline the functions in it to the maximum possible extent.
@@ -31,27 +42,6 @@ let inline ({main; functions} as orig_prog : program) : program option =
3142
VarSet.union (all_declared_vars instrs) (VarSet.of_list formals)
3243
in
3344

34-
(* Given a function and an array of instructions, generate a fresh version
35-
for the function with these instructions. *)
36-
let add_version (func : afunction) (instrs : instructions) =
37-
let label = fresh_version_label func "inlined_version" in
38-
let version = {instrs = instrs; label = label; annotations = None} in
39-
{func with body = version :: func.body}
40-
in
41-
42-
(* Given `instructions` and the function identifier, extract the location,
43-
return variable and arguments from the first callsite where a call is made
44-
to that function *)
45-
let extract_callsite instrs fun_ref : (pc * variable * argument list) =
46-
let rec at pc =
47-
match[@warning "-4"] instrs.(pc) with
48-
| Call (x, (Simple (Constant (Fun_ref f))), es) ->
49-
if f = fun_ref then (pc, x, es) else at (pc + 1)
50-
| _ -> at (pc + 1)
51-
in
52-
at 0
53-
in
54-
5545
(* Replace variables in `callee` instructions so that they don't match the
5646
`caller` variables. The formals list of the callee is also updated
5747
accordingly *)
@@ -103,8 +93,7 @@ let inline ({main; functions} as orig_prog : program) : program option =
10393

10494
(* Replace labels in `callee` instructions so that they don't match the
10595
`caller` labels *)
106-
let replace_labels caller ({formals; instrs} as callee) =
107-
let used_labels = extract_labels caller.instrs in
96+
let replace_labels used_labels ({formals; instrs} as callee) =
10897
let callee_labels = LabelSet.elements (extract_labels callee.instrs) in
10998
let replacements = Edit.fresh_many used_labels callee_labels in
11099
let mapper instr =
@@ -116,7 +105,8 @@ let inline ({main; functions} as orig_prog : program) : program option =
116105
| Branch (e, l1, l2) -> Branch (e, replace l1, replace l2)
117106
| i -> i
118107
in
119-
{callee with instrs = Array.map mapper instrs}
108+
let new_labels = LabelSet.of_list (snd (List.split replacements)) in
109+
{callee with instrs = Array.map mapper instrs}, LabelSet.union used_labels new_labels
120110
in
121111

122112
(* Inserts the header for the inlined callee body. Assigns all the formals to
@@ -141,10 +131,10 @@ let inline ({main; functions} as orig_prog : program) : program option =
141131

142132
(* Transforms the callee instructions to a form that can be substituted
143133
inside the caller.*)
144-
let compose caller callee ret_var arguments : inlining_input =
145-
let callee = callee
146-
|> replace_vars caller
147-
|> replace_labels caller
134+
let compose caller callee used_labels ret_var arguments =
135+
let callee, used_labels = callee
136+
|> replace_vars caller
137+
|> replace_labels used_labels
148138
in
149139
(* It is important to generate return label and result variable after
150140
replacing variables and labels. Otherwise there is a good chance that
@@ -154,108 +144,103 @@ let inline ({main; functions} as orig_prog : program) : program option =
154144
callee with `res_1` and `res_1` is already used for the result variable.
155145
This problem will not happen if we `replace_vars` before generating a
156146
fresh `res_var`. *)
157-
let ret_lab =
158-
fresh_label (Array.append callee.instrs caller.instrs) "inl"
159-
in
147+
let ret_lab = fresh used_labels "inl" in
148+
let used_labels = LabelSet.add ret_lab used_labels in
149+
160150
let res_var =
161151
fresh (VarSet.union (function_vars callee) (function_vars caller)) "res"
162152
in
163-
callee
164-
|> replace_returns res_var ret_lab
165-
|> insert_prologue res_var arguments
166-
|> insert_epilogue res_var ret_var ret_lab
167-
in
168-
169-
(* Given the caller and callee identifiers, generate a fresh version of caller
170-
with the callee inlined.*)
171-
let inline_pair (caller_id : identifier)
172-
(callee_id : identifier)
173-
(prog : program) =
174-
let caller = lookup_fun prog caller_id in
175-
let caller' = as_inlining_input caller (List.hd caller.body) in
176-
let callee = lookup_fun prog callee_id in
177-
let callee' = as_inlining_input callee (List.hd callee.body) in
178-
let (pc, ret_var, arguments) = extract_callsite caller'.instrs callee_id in
179-
let callee'' = compose caller' callee' ret_var arguments in
180-
let instrs, _ = subst caller'.instrs pc 1 callee''.instrs in
181-
add_version caller instrs
153+
let res = callee
154+
|> replace_returns res_var ret_lab
155+
|> insert_prologue res_var arguments
156+
|> insert_epilogue res_var ret_var ret_lab in
157+
(res, used_labels)
182158
in
183159

184-
(* This function computes an order for inlining of the entire program.
185-
Given a call graph starting from main, it descends as deep as possible into
186-
the call chain and stops when it encounters an edge which leads to recursion.
187-
Using this depth-first approach, it generates all caller-callee pairs with
188-
the depth at which this pair was generated. This depth is used to sort the
189-
list of these pairs such that the inlining happens in a bottom up fashion.
190-
If a caller-callee pair is already encountered, then it is not included
191-
again. This is because once the callee has been inlined in the caller at a
192-
given callsite, then the callsite no longer exists. However, multiple
193-
callsites with the same target will result in as many caller-callee pairs.
194-
Note - The word reduced is used in the general sense of something being
195-
simplified. If a function has been analyzed for inlining, its considered
196-
reduced. In reality, inlining expands this function.
197-
The accumulator `acc` is composed of the following components -
198-
- `ord` - The list of (caller name, callee name, depth) triplets
199-
- `caller` - The caller object
200-
- `vis` - The set of visited function names
201-
- `red` - The set of reduced (already analyzed for inlining) function names
202-
- `dep` - The depth of this function from `main`
203-
*)
204-
let compute_inlining_order (prog : program) : (identifier * identifier) list =
205-
let rec inspect_instr ((ord, caller, vis, red, dep) as acc) instr =
206-
match[@warning "-4"] instr with
160+
(* This function computes an order for inlining of the entire program.
161+
Given a call graph starting from main, it descends as deep as possible into
162+
the call chain and stops when it encounters an edge which leads to recursion.
163+
*)
164+
let rec compute_inline_order func seen : inlining_site =
165+
let instrs = (active_version func).instrs in
166+
let inlinings = ref [] in
167+
let visit_instr pc =
168+
assert (pc+1 < Array.length instrs);
169+
match[@warning "-4"] instrs.(pc) with
207170
| Call (x, (Simple (Constant (Fun_ref f))), es) ->
208-
(* If `f` is already visited in this branch, then there is recursion,
209-
so don't add this pair to accumulator.*)
210-
if (VarSet.mem f vis) then acc
211-
(* If `f` is already analyzed for inlining, then add it but don't go
212-
inside it.*)
213-
else if (VarSet.mem f red)
214-
then ((caller.name, f, dep) :: ord, caller, vis, red, dep)
215-
(* If `f` is neither visited in this branch, nor has it been analyzed
216-
for inlining before, then descend into it and analyze it.*)
217-
else
218-
let callee = lookup_fun prog f in
219-
let (callee_ord, _, _, callee_red, _) =
220-
add_callees callee vis red (dep + 1)
221-
in
222-
let new_ord = (caller.name, f, dep) :: ord @ callee_ord in
223-
let new_red = VarSet.union red callee_red in
224-
(* Note that visited set is only maintained for a branch while
225-
reduced set is carried across branches. *)
226-
(new_ord, caller, vis, new_red, dep)
227-
(* Any other instruction returns accumulator unchanged. *)
228-
| _ -> acc
229-
and add_callees caller vis red dep =
230-
(* The `caller` is being visited and reduced. So add it to the two sets.*)
231-
let vis = VarSet.add caller.name vis in
232-
let red = VarSet.add caller.name red in
233-
Array.fold_left
234-
inspect_instr
235-
([], caller, vis, red, dep)
236-
(List.hd caller.body).instrs
171+
if LabelSet.mem f seen then () else begin
172+
(* If the call is followed by a checkpoint we store it's
173+
* osr map to be able to concatenate it to the inlinee's *)
174+
let checkpoint = (match[@warning "-4"] instrs.(pc+1) with
175+
| Osr {varmap; frame_maps} -> Some []
176+
| _ -> None) in
177+
let seen = LabelSet.add f seen in
178+
let callee = lookup_fun orig_prog f in
179+
let next = compute_inline_order callee seen in
180+
let inlining = { pos = pc; target = callee; ret = x; args = es; osr = checkpoint; next } in
181+
inlinings := inlining :: !inlinings
182+
end
183+
| _ -> ()
237184
in
238-
(* Sort (caller, callee, depth) triplets in decreasing order of depth. *)
239-
let comp (_, _, d1) (_, _, d2) = d2 - d1 in
240-
let (ord, _, _, _, _) = add_callees main VarSet.empty VarSet.empty 1 in
241-
List.map
242-
(fun (caller_id, callee_id, _ ) -> (caller_id, callee_id))
243-
(List.sort comp ord)
185+
for pc = 0 to (Array.length instrs) - 2 do
186+
visit_instr pc
187+
done;
188+
!inlinings
189+
in
190+
191+
let needs_osr =
192+
let is_osr = function[@warning "-4"]
193+
| Osr _ -> true | _ -> false in
194+
Array.exists is_osr
195+
in
196+
197+
let rec apply_inlinings func inlinings =
198+
let cur = as_inlining_input func in
199+
if inlinings = []
200+
then cur
201+
else
202+
let used_labels = ref (extract_labels cur.instrs) in
203+
let get {target; next; pos; ret; args; osr} =
204+
let apply next = if next = []
205+
then as_inlining_input target
206+
else apply_inlinings target next in
207+
let callee = apply next in
208+
match needs_osr callee.instrs, osr with
209+
| false, _ ->
210+
let inlinee, new_used = compose cur callee !used_labels ret args in
211+
used_labels := new_used;
212+
(pos, 1, inlinee.instrs)
213+
| true, Some osr ->
214+
(* TODO(osr): Here we need to update the inlinee osr points to create the
215+
* additional osr frame. *)
216+
(pos, 0, [| |])
217+
| true , None ->
218+
(* The callee needs to osr but the caller does not have a safepoint
219+
* after the call. We can't do anything. *)
220+
(pos, 0, [| |])
221+
in
222+
let to_inline = List.map get inlinings in
223+
let instrs, _ = Edit.subst_many cur.instrs to_inline in
224+
{ cur with instrs }
244225
in
245226

246-
(* Given a list of caller - callee pairs, inline the callee inside the caller
247-
until the list is exhausted. Order matters, as only this order will result
248-
in a maximally inlined program excluding recursive calls. The final program
249-
will only have recursive calls left to be inlined. *)
250-
let inline_with order prog =
251-
List.fold_left
252-
(fun p (caller, callee) -> replace_fun p (inline_pair caller callee p))
253-
prog
254-
order
227+
let inline_at func =
228+
let inline_order = compute_inline_order func LabelSet.empty in
229+
(* If there are no caller-callee pairs to inline, return `None`, else return
230+
the completely inlined program *)
231+
if inline_order = [] then None
232+
else
233+
let result = apply_inlinings func inline_order in
234+
let label = fresh_version_label func "inlined_version" in
235+
Some { label; annotations = None; instrs = result.instrs }
255236
in
256237

257-
(* If there are no caller-callee pairs to inline, return `None`, else return
258-
the completely inlined program *)
259-
let inline_order = compute_inlining_order orig_prog in
260-
if List.length inline_order = 0 then None
261-
else Some (inline_with inline_order orig_prog)
238+
(* Starting from main inline all the way down *)
239+
match inline_at orig_prog.main with
240+
| None -> None
241+
| Some v ->
242+
Some { orig_prog with
243+
main = { orig_prog.main with
244+
body = v :: orig_prog.main.body
245+
}
246+
}

0 commit comments

Comments
 (0)