@@ -9,8 +9,19 @@ type inlining_input = {
9
9
instrs : instructions ;
10
10
}
11
11
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
+
14
25
15
26
(* FUNCTION INLINING *)
16
27
(* 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 =
31
42
VarSet. union (all_declared_vars instrs) (VarSet. of_list formals)
32
43
in
33
44
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
-
55
45
(* Replace variables in `callee` instructions so that they don't match the
56
46
`caller` variables. The formals list of the callee is also updated
57
47
accordingly *)
@@ -103,8 +93,7 @@ let inline ({main; functions} as orig_prog : program) : program option =
103
93
104
94
(* Replace labels in `callee` instructions so that they don't match the
105
95
`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 ) =
108
97
let callee_labels = LabelSet. elements (extract_labels callee.instrs) in
109
98
let replacements = Edit. fresh_many used_labels callee_labels in
110
99
let mapper instr =
@@ -116,7 +105,8 @@ let inline ({main; functions} as orig_prog : program) : program option =
116
105
| Branch (e , l1 , l2 ) -> Branch (e, replace l1, replace l2)
117
106
| i -> i
118
107
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
120
110
in
121
111
122
112
(* 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 =
141
131
142
132
(* Transforms the callee instructions to a form that can be substituted
143
133
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
148
138
in
149
139
(* It is important to generate return label and result variable after
150
140
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 =
154
144
callee with `res_1` and `res_1` is already used for the result variable.
155
145
This problem will not happen if we `replace_vars` before generating a
156
146
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
+
160
150
let res_var =
161
151
fresh (VarSet. union (function_vars callee) (function_vars caller)) " res"
162
152
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)
182
158
in
183
159
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
207
170
| 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
+ | _ -> ()
237
184
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 }
244
225
in
245
226
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 }
255
236
in
256
237
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