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
|
(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Xavier Leroy and Damien Doligez, projet Cambium, INRIA Paris *)
(* Sadiq Jaffer, OCaml Labs Consultancy Ltd *)
(* Stephen Dolan and Mark Shinwell, Jane Street Europe *)
(* *)
(* Copyright 2021 Institut National de Recherche en Informatique et *)
(* en Automatique. *)
(* Copyright 2021 OCaml Labs Consultancy Ltd *)
(* Copyright 2021 Jane Street Group LLC *)
(* *)
(* All rights reserved. This file is distributed under the terms of *)
(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
open Mach
open Format_doc
module Int = Numbers.Int
module String = Misc.Stdlib.String
let function_is_assumed_to_never_poll func =
String.starts_with ~prefix:"caml_apply" func
|| String.starts_with ~prefix:"caml_send" func
(* These are used for the poll error annotation later on*)
type polling_point = Alloc | Poll | Function_call | External_call
type error = Poll_error of (polling_point * Debuginfo.t) list
exception Error of error
(* Detection of recursive handlers that are not guaranteed to poll
at every loop iteration. *)
(* We use a backwards dataflow analysis to compute a mapping from handlers H
(= loop heads) to either "safe" or "unsafe".
H is "safe" if every path starting from H goes through an Ialloc,
Ipoll, Ireturn, Itailcall_ind or Itailcall_imm instruction.
H is "unsafe", therefore, if starting from H we can loop infinitely
without crossing an Ialloc or Ipoll instruction.
*)
type unsafe_or_safe = Unsafe | Safe
module Unsafe_or_safe = struct
type t = unsafe_or_safe
let bot = Unsafe
let join t1 t2 =
match t1, t2 with
| Unsafe, Unsafe
| Unsafe, Safe
| Safe, Unsafe -> Unsafe
| Safe, Safe -> Safe
let lessequal t1 t2 =
match t1, t2 with
| Unsafe, Unsafe
| Unsafe, Safe
| Safe, Safe -> true
| Safe, Unsafe -> false
end
module PolledLoopsAnalysis = Dataflow.Backward(Unsafe_or_safe)
let polled_loops_analysis funbody =
let transfer i ~next ~exn =
match i.desc with
| Iend -> next
| Iop (Ialloc _ | Ipoll _)
| Iop (Itailcall_ind | Itailcall_imm _) -> Safe
| Iop op ->
if operation_can_raise op
then Unsafe_or_safe.join next exn
else next
| Ireturn -> Safe
| Iifthenelse _ | Iswitch _ | Icatch _ | Iexit _ | Itrywith _ -> next
| Iraise _ -> exn
in
(* [exnescape] is [Safe] because we can't loop infinitely having
returned from the function via an unhandled exception. *)
snd (PolledLoopsAnalysis.analyze ~exnescape:Safe ~transfer funbody)
(* Detection of functions that can loop via a tail-call without going
through a poll point. *)
(* We use a backwards dataflow analysis to compute a single value: either
"Might_not_poll" or "Always_polls".
"Might_not_poll" means there exists a path from the function entry to a
Potentially Recursive Tail Call (an Itailcall_ind or
Itailcall_imm to a forward function)
that does not go through an Ialloc or Ipoll instruction.
"Always_polls", therefore, means the function always polls (via Ialloc or
Ipoll) before doing a PRTC. This includes the case where it does not
perform any PRTC.
A note on Potentially Recursive Tail Calls
------------------------------------------
Tail calls can create infinite loops, of course. (Consider a function
that tail-calls itself.) But not all tail calls need to be flagged
as potential infinite loops.
We optimise by making a partial ordering over Mach functions: in
definition order within a compilation unit, and dependency
order between compilation units. This order is acyclic, as
OCaml does not allow circular dependencies between modules.
It's also finite, so if there's an infinite sequence of
function calls then something has to make a forward reference.
Also, in such an infinite sequence of function calls, at most finitely
many of them can be non-tail calls. (If there are infinitely many
non-tail calls, then the program soon terminates with a stack
overflow).
So, every such infinite sequence must contain many forward-referencing
tail calls. These tail calls are the Potentially Recursive Tail Calls
(PTRCs). Polling only on those calls suffices.
Several functions below take a parameter [future_funcnames]
which is the set of functions defined "after" the current function
in the current compilation unit. The PTRCs are tail calls
to known functions in [future_funcnames], or tail calls to
unknown functions.
*)
type polls_before_prtc = Might_not_poll | Always_polls
module Polls_before_prtc = struct
type t = polls_before_prtc
let bot = Always_polls
let join t1 t2 =
match t1, t2 with
| Might_not_poll, Might_not_poll
| Might_not_poll, Always_polls
| Always_polls, Might_not_poll -> Might_not_poll
| Always_polls, Always_polls -> Always_polls
let lessequal t1 t2 =
match t1, t2 with
| Always_polls, Always_polls
| Always_polls, Might_not_poll
| Might_not_poll, Might_not_poll -> true
| Might_not_poll, Always_polls -> false
end
module PTRCAnalysis = Dataflow.Backward(Polls_before_prtc)
let potentially_recursive_tailcall ~future_funcnames funbody =
let transfer i ~next ~exn =
match i.desc with
| Iend -> next
| Iop (Ialloc _ | Ipoll _) -> Always_polls
| Iop (Itailcall_ind) -> Might_not_poll (* this is a PTRC *)
| Iop (Itailcall_imm { func }) ->
if String.Set.mem func future_funcnames
|| function_is_assumed_to_never_poll func
then Might_not_poll (* this is a PTRC *)
else Always_polls (* this is not a PTRC *)
| Iop op ->
if operation_can_raise op
then Polls_before_prtc.join next exn
else next
| Ireturn -> Always_polls
| Iifthenelse _ | Iswitch _ | Icatch _ | Iexit _ | Itrywith _ -> next
| Iraise _ -> exn
in
fst (PTRCAnalysis.analyze ~transfer funbody)
(* We refer to the set of recursive handler labels that need extra polling
as the "unguarded back edges" ("ube").
Given the result of the analysis of recursive handlers, add [Ipoll]
instructions at the [Iexit] instructions before unguarded back edges,
thus ensuring that every loop contains a poll point. Also compute whether
the resulting function contains any [Ipoll] instructions.
*)
let add_poll i =
Mach.instr_cons_debug (Iop (Ipoll { return_label = None })) [||] [||] i.dbg i
let instr_body handler_safe i =
let add_unsafe_handler ube (k, _) =
match handler_safe k with
| Safe -> ube
| Unsafe -> Int.Set.add k ube
in
let rec instr ube i =
match i.desc with
| Iifthenelse (test, i0, i1) ->
{ i with
desc = Iifthenelse (test, instr ube i0, instr ube i1);
next = instr ube i.next;
}
| Iswitch (index, cases) ->
{ i with
desc = Iswitch (index, Array.map (instr ube) cases);
next = instr ube i.next;
}
| Icatch (rc, hdl, body) ->
let ube' =
match rc with
| Cmm.Recursive -> List.fold_left add_unsafe_handler ube hdl
| Cmm.Nonrecursive -> ube in
let instr_handler (k, i0) =
let i1 = instr ube' i0 in
(k, i1) in
(* Since we are only interested in unguarded _back_ edges, we don't
use [ube'] for instrumenting [body], but just [ube] instead. *)
let body = instr ube body in
{ i with
desc = Icatch (rc,
List.map instr_handler hdl,
body);
next = instr ube i.next;
}
| Iexit k ->
if Int.Set.mem k ube
then add_poll i
else i
| Itrywith (body, hdl) ->
{ i with
desc = Itrywith (instr ube body, instr ube hdl);
next = instr ube i.next;
}
| Iend | Ireturn | Iraise _ -> i
| Iop _ ->
{ i with next = instr ube i.next }
in
instr Int.Set.empty i
let find_poll_alloc_or_calls instr =
let f_match i =
match i.desc with
| Iop(Ipoll _) -> Some (Poll, i.dbg)
| Iop(Ialloc _) -> Some (Alloc, i.dbg)
| Iop(Icall_ind | Icall_imm _ |
Itailcall_ind | Itailcall_imm _ ) -> Some (Function_call, i.dbg)
| Iop(Iextcall { alloc = true }) -> Some (External_call, i.dbg)
| Iop(Imove | Ispill | Ireload | Iconst_int _ | Iconst_float _ |
Iconst_symbol _ | Iextcall { alloc = false } | Istackoffset _ |
Iload _ | Istore _ | Iintop _ | Iintop_imm _ | Ifloatofint |
Iintoffloat | Inegf | Iabsf | Iaddf | Isubf | Imulf | Idivf |
Iopaque | Ispecific _ | Idls_get | Icompf _ | Ireturn_addr) -> None
| Iend | Ireturn | Iifthenelse _ | Iswitch _ | Icatch _ | Iexit _ |
Itrywith _ | Iraise _ -> None
in
let matches = ref [] in
Mach.instr_iter
(fun i ->
match f_match i with
| Some(x) -> matches := x :: !matches
| None -> ())
instr;
List.rev !matches
let instrument_fundecl ~future_funcnames:_ (f : Mach.fundecl) : Mach.fundecl =
if function_is_assumed_to_never_poll f.fun_name then f
else begin
let handler_needs_poll = polled_loops_analysis f.fun_body in
let new_body = instr_body handler_needs_poll f.fun_body in
begin match f.fun_poll with
| Error_poll -> begin
match find_poll_alloc_or_calls new_body with
| [] -> ()
| poll_error_instrs -> raise (Error(Poll_error poll_error_instrs))
end
| Default_poll -> () end;
{ f with fun_body = new_body }
end
let requires_prologue_poll ~future_funcnames ~fun_name i =
if function_is_assumed_to_never_poll fun_name then false
else
match potentially_recursive_tailcall ~future_funcnames i with
| Might_not_poll -> true
| Always_polls -> false
(* Error report *)
let instr_type p =
match p with
| Poll -> "inserted poll"
| Alloc -> "allocation"
| Function_call -> "function call"
| External_call -> "external call that allocates"
let report_error ppf = function
| Poll_error instrs ->
begin
let num_inserted_polls =
List.fold_left
(fun s (p,_) -> s + match p with Poll -> 1
| Alloc | Function_call | External_call -> 0
) 0 instrs in
let num_user_polls = (List.length instrs) - num_inserted_polls in
if num_user_polls = 0 then
fprintf ppf "Function with poll-error attribute contains polling \
points (inserted by the compiler)\n"
else begin
fprintf ppf
"Function with poll-error attribute contains polling points:\n";
List.iter (fun (p,dbg) ->
begin match p with
| Poll -> ()
| Alloc | Function_call | External_call ->
fprintf ppf "\t%s at " (instr_type p);
Location.Doc.loc ppf (Debuginfo.to_location dbg);
fprintf ppf "\n"
end
) instrs;
if num_inserted_polls > 0 then
fprintf ppf "\t(plus compiler-inserted polling point(s) in prologue \
and/or loop back edges)\n"
end
end
let () =
Location.register_error_of_exn
(function
| Error err -> Some (Location.error_of_printer_file report_error err)
| _ -> None
)
|