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 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
|
(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Pierre Chambart, OCamlPro *)
(* Mark Shinwell and Leo White, Jane Street Europe *)
(* *)
(* Copyright 2013--2016 OCamlPro SAS *)
(* Copyright 2014--2016 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. *)
(* *)
(**************************************************************************)
[@@@ocaml.warning "+a-4-9-30-40-41-42-66"]
open! Int_replace_polymorphic_compare
(* This cannot be done in a single simple pass due to expressions like:
let rec ... =
...
let rec f1 x =
let f2 y =
f1 rec_list
in
f2 v
and rec_list = f1 :: rec_list in
...
and v = ...
f1, f2 and rec_list are constants iff v is a constant.
To handle this we populate both a 'not constant' set NC and a set of
implications between variables.
For example, the above code would generate the implications:
f1 in NC => rec_list in NC
f2 in NC => f1 in NC
rec_list in NC => f2 in NC
v in NC => f1 in NC
then if v is found to be in NC this will be propagated to place
f1, f2 and rec_list in NC as well.
*)
(* CR-someday lwhite: I think this pass could be combined with
alias_analysis and other parts of lift_constants into a single
type-based analysis which infers a "type" for each variable that is
either an allocated_constant expression or "not constant". Recursion
would be handled with unification variables. *)
module Int = Numbers.Int
module Symbol_field = struct
type t = Symbol.t * Int.t
include Identifiable.Make (Identifiable.Pair (Symbol) (Int))
end
type dep =
| Closure of Set_of_closures_id.t
| Var of Variable.t
| Symbol of Symbol.t
| Symbol_field of Symbol_field.t
type state =
| Not_constant
| Implication of dep list
type result = {
id : state Variable.Tbl.t;
closure : state Set_of_closures_id.Tbl.t;
}
module type Param = sig
val program : Flambda.program
val compilation_unit : Compilation_unit.t
end
(* CR-soon mshinwell: consider removing functor *)
module Inconstants (P:Param) (Backend:Backend_intf.S) = struct
let program = P.program
let compilation_unit = P.compilation_unit
let imported_symbols = Flambda_utils.imported_symbols program
(* Sets representing NC *)
let variables : state Variable.Tbl.t = Variable.Tbl.create 42
let closures : state Set_of_closures_id.Tbl.t =
Set_of_closures_id.Tbl.create 42
let symbols : state Symbol.Tbl.t = Symbol.Tbl.create 42
let symbol_fields : state Symbol_field.Tbl.t = Symbol_field.Tbl.create 42
let mark_queue = Queue.create ()
(* CR-soon pchambart: We could probably improve that quite a lot by adding
(the future annotation) [@unrolled] at the right call sites. Or more
directly mark mark_dep as [@inline] and call it instead of mark_curr in
some situations.
*)
(* adds 'dep in NC' *)
let rec mark_dep = function
| Var id -> begin
match Variable.Tbl.find variables id with
| Not_constant -> ()
| Implication deps ->
Variable.Tbl.replace variables id Not_constant;
Queue.push deps mark_queue
| exception Not_found ->
Variable.Tbl.add variables id Not_constant
end
| Closure cl -> begin
match Set_of_closures_id.Tbl.find closures cl with
| Not_constant -> ()
| Implication deps ->
Set_of_closures_id.Tbl.replace closures cl Not_constant;
Queue.push deps mark_queue
| exception Not_found ->
Set_of_closures_id.Tbl.add closures cl Not_constant
end
| Symbol s -> begin
match Symbol.Tbl.find symbols s with
| Not_constant -> ()
| Implication deps ->
Symbol.Tbl.replace symbols s Not_constant;
Queue.push deps mark_queue
| exception Not_found ->
Symbol.Tbl.add symbols s Not_constant
end
| Symbol_field s -> begin
match Symbol_field.Tbl.find symbol_fields s with
| Not_constant -> ()
| Implication deps ->
Symbol_field.Tbl.replace symbol_fields s Not_constant;
Queue.push deps mark_queue
| exception Not_found ->
Symbol_field.Tbl.add symbol_fields s Not_constant
end
and mark_deps deps =
List.iter mark_dep deps
and complete_marking () =
while not (Queue.is_empty mark_queue) do
let deps =
try
Queue.take mark_queue
with Not_found -> []
in
mark_deps deps;
done
(* adds 'curr in NC' *)
let mark_curr curr =
mark_deps curr;
complete_marking ()
(* adds in the tables 'dep in NC => curr in NC' *)
let register_implication ~in_nc:dep ~implies_in_nc:curr =
match dep with
| Var id -> begin
match Variable.Tbl.find variables id with
| Not_constant ->
mark_deps curr;
complete_marking ();
| Implication deps ->
let deps = List.rev_append curr deps in
Variable.Tbl.replace variables id (Implication deps)
| exception Not_found ->
Variable.Tbl.add variables id (Implication curr);
end
| Closure cl -> begin
match Set_of_closures_id.Tbl.find closures cl with
| Not_constant ->
mark_deps curr;
complete_marking ();
| Implication deps ->
let deps = List.rev_append curr deps in
Set_of_closures_id.Tbl.replace closures cl (Implication deps)
| exception Not_found ->
Set_of_closures_id.Tbl.add closures cl (Implication curr);
end
| Symbol symbol -> begin
match Symbol.Tbl.find symbols symbol with
| Not_constant ->
mark_deps curr;
complete_marking ();
| Implication deps ->
let deps = List.rev_append curr deps in
Symbol.Tbl.replace symbols symbol (Implication deps)
| exception Not_found ->
Symbol.Tbl.add symbols symbol (Implication curr);
end
| Symbol_field ((symbol, _) as field) -> begin
match Symbol_field.Tbl.find symbol_fields field with
| Not_constant ->
mark_deps curr;
complete_marking ();
| Implication deps ->
let deps = List.rev_append curr deps in
Symbol_field.Tbl.replace symbol_fields field (Implication deps)
| exception Not_found ->
(* There is no information available about the contents of imported
symbols, so we must consider all their fields as inconstant. *)
(* CR-someday pchambart: recover that from the cmx information *)
if Symbol.Set.mem symbol imported_symbols then begin
Symbol_field.Tbl.add symbol_fields field Not_constant;
mark_deps curr;
complete_marking ();
end else begin
Symbol_field.Tbl.add symbol_fields field (Implication curr)
end
end
(* First loop: iterates on the tree to mark dependencies.
curr is the variables or closures to which we add constraints like
'... in NC => curr in NC' or 'curr in NC'
It can be empty when no constraint can be added like in the toplevel
expression or in the body of a function.
*)
let rec mark_loop ~toplevel (curr : dep list) (flam : Flambda.t) =
match flam with
| Let { var; defining_expr = lam; body; _ } ->
mark_named ~toplevel [Var var] lam;
(* adds 'var in NC => curr in NC'
This is not really necessary, but compiling this correctly is
trickier than eliminating that earlier. *)
mark_var var curr;
mark_loop ~toplevel curr body
| Let_mutable { initial_value = var; body } ->
mark_var var curr;
mark_loop ~toplevel curr body
| Var var -> mark_var var curr
(* Not constant cases: we mark directly 'curr in NC' and mark
bound variables as in NC also *)
| Assign _ ->
mark_curr curr
| Try_with (f1,id,f2) ->
mark_curr [Var id];
mark_curr curr;
mark_loop ~toplevel [] f1;
mark_loop ~toplevel [] f2
| Static_catch (_,ids,f1,f2) ->
List.iter (fun (id, _) -> mark_curr [Var id]) ids;
mark_curr curr;
mark_loop ~toplevel [] f1;
mark_loop ~toplevel [] f2
(* CR-someday pchambart: If recursive staticcatch is introduced:
this becomes ~toplevel:false *)
| For { bound_var; from_value; to_value; direction = _; body; } ->
mark_curr [Var bound_var];
mark_var from_value curr;
mark_var to_value curr;
mark_curr curr;
mark_loop ~toplevel:false [] body
| While (f1,body) ->
mark_curr curr;
mark_loop ~toplevel [] f1;
mark_loop ~toplevel:false [] body
| If_then_else (cond,f2,f3) ->
mark_curr curr;
mark_var cond curr;
mark_loop ~toplevel [] f2;
mark_loop ~toplevel [] f3
| Static_raise (_,l) ->
mark_curr curr;
List.iter (fun v -> mark_var v curr) l
| Apply ({func; args; _ }) ->
mark_curr curr;
mark_var func curr;
mark_vars args curr;
| Switch (arg,sw) ->
mark_curr curr;
mark_var arg curr;
List.iter (fun (_,l) -> mark_loop ~toplevel [] l) sw.consts;
List.iter (fun (_,l) -> mark_loop ~toplevel [] l) sw.blocks;
Option.iter (fun l -> mark_loop ~toplevel [] l) sw.failaction
| String_switch (arg,sw,def) ->
mark_curr curr;
mark_var arg curr;
List.iter (fun (_,l) -> mark_loop ~toplevel [] l) sw;
Option.iter (fun l -> mark_loop ~toplevel [] l) def
| Send { kind = _; meth; obj; args; dbg = _; } ->
mark_curr curr;
mark_var meth curr;
mark_var obj curr;
List.iter (fun arg -> mark_var arg curr) args
| Proved_unreachable ->
mark_curr curr
and mark_named ~toplevel curr (named : Flambda.named) =
match named with
| Set_of_closures (set_of_closures) ->
mark_loop_set_of_closures ~toplevel curr set_of_closures
| Const _ | Allocated_const _ -> ()
| Read_mutable _ -> mark_curr curr
| Symbol symbol -> begin
let current_unit = Compilation_unit.get_current_exn () in
if Compilation_unit.equal current_unit (Symbol.compilation_unit symbol)
then
()
else
match (Backend.import_symbol symbol).descr with
| Value_unresolved _ ->
(* Constant when 'for_clambda' means: can be a symbol (which is
obviously the case here) with a known approximation. If this
condition is not satisfied we mark as inconstant to reflect
the fact that the symbol's contents are unknown and thus
prevent attempts to examine it. (This is a bit of a hack.) *)
mark_curr curr
| _ ->
()
end
| Read_symbol_field (symbol, index) ->
register_implication ~in_nc:(Symbol_field (symbol, index))
~implies_in_nc:curr
(* Constant constructors: those expressions are constant if all their
parameters are:
- makeblock is compiled to a constant block
- offset is compiled to a pointer inside a constant closure.
See Cmmgen for the details
makeblock(Mutable) can be a 'constant' if it is allocated at
toplevel: if this expression is evaluated only once.
*)
| Prim (Pmakeblock (_tag, Asttypes.Immutable, _value_kind), args,
_dbg) ->
mark_vars args curr
(* (* CR-someday pchambart: If global mutables are allowed: *)
| Prim(Lambda.Pmakeblock(_tag, Asttypes.Mutable), args, _dbg, _)
when toplevel ->
List.iter (mark_loop ~toplevel curr) args
*)
| Prim (Pmakearray (Pfloatarray, Immutable), args, _) ->
mark_vars args curr
| Prim (Pmakearray (Pfloatarray, Mutable), args, _) ->
(* CR-someday pchambart: Toplevel float arrays could always be
statically allocated using an equivalent of the
Initialize_symbol construction.
Toplevel non-float arrays could also be turned into an
Initialize_symbol, but only when declared as immutable since
preallocated symbols does not allow mutation after
initialisation
*)
if toplevel then mark_vars args curr
else mark_curr curr
| Prim (Pduparray (Pfloatarray, Immutable), [arg], _) ->
mark_var arg curr
| Prim (Pduparray (Pfloatarray, Mutable), [arg], _) ->
if toplevel then mark_var arg curr
else mark_curr curr
| Prim (Pduparray _, _, _) ->
(* See Lift_constants *)
mark_curr curr
| Project_closure ({ set_of_closures; closure_id; }) ->
if Closure_id.in_compilation_unit closure_id compilation_unit then
mark_var set_of_closures curr
else
mark_curr curr
| Move_within_set_of_closures ({ closure; start_from; move_to; }) ->
(* CR-someday mshinwell: We should be able to deem these projections
(same for the cases below) as constant when from another
compilation unit, but there isn't code to handle this yet. (Note
that for Project_var we cannot yet generate a projection from a
closure in another compilation unit, since we only lift closed
closures.) *)
if Closure_id.in_compilation_unit start_from compilation_unit then begin
assert (Closure_id.in_compilation_unit move_to compilation_unit);
mark_var closure curr
end else begin
mark_curr curr
end
| Project_var ({ closure; closure_id; var = _ }) ->
if Closure_id.in_compilation_unit closure_id compilation_unit then
mark_var closure curr
else
mark_curr curr
| Prim (Pfield _, [f1], _) ->
mark_curr curr;
mark_var f1 curr
| Prim (_, args, _) ->
mark_curr curr;
mark_vars args curr
| Expr flam ->
mark_loop ~toplevel curr flam
and mark_var var curr =
(* adds 'id in NC => curr in NC' *)
register_implication ~in_nc:(Var var) ~implies_in_nc:curr
and mark_vars vars curr =
(* adds 'id in NC => curr in NC' *)
List.iter (fun var -> mark_var var curr) vars
(* [toplevel] is intended for allowing static allocations of mutable
blocks. This feature should be available in a future release once the
necessary GC changes have been merged. (See GPR#178.) *)
and mark_loop_set_of_closures ~toplevel:_ curr
{ Flambda. function_decls; free_vars; specialised_args } =
(* If a function in the set of closures is specialised, do not consider
it constant, unless all specialised args are also constant. *)
Variable.Map.iter (fun _ (spec_arg : Flambda.specialised_to) ->
register_implication
~in_nc:(Var spec_arg.var)
~implies_in_nc:[Closure function_decls.set_of_closures_id])
specialised_args;
(* adds 'function_decls in NC => curr in NC' *)
register_implication ~in_nc:(Closure function_decls.set_of_closures_id)
~implies_in_nc:curr;
(* a closure is constant if its free variables are constants. *)
Variable.Map.iter (fun inner_id (var : Flambda.specialised_to) ->
register_implication ~in_nc:(Var var.var)
~implies_in_nc:[
Var inner_id;
Closure function_decls.set_of_closures_id
])
free_vars;
Variable.Map.iter (fun fun_id (ffunc : Flambda.function_declaration) ->
(* for each function f in a closure c 'c in NC => f' *)
register_implication ~in_nc:(Closure function_decls.set_of_closures_id)
~implies_in_nc:[Var fun_id];
(* function parameters are in NC unless specialised *)
List.iter (fun param ->
match Variable.Map.find param specialised_args with
| exception Not_found -> mark_curr [Var param]
| outer_var ->
register_implication ~in_nc:(Var outer_var.var)
~implies_in_nc:[Var param])
(Parameter.List.vars ffunc.params);
mark_loop ~toplevel:false [] ffunc.body)
function_decls.funs
let mark_constant_defining_value (const:Flambda.constant_defining_value) =
match const with
| Allocated_const _
| Block _
| Project_closure _ -> ()
| Set_of_closures set_of_closure ->
mark_loop_set_of_closures ~toplevel:true [] set_of_closure
let mark_program (program : Flambda.program) =
let rec loop (program : Flambda.program_body) =
match program with
| End _ -> ()
| Initialize_symbol (symbol,_tag,fields,program) ->
List.iteri (fun i field ->
mark_loop ~toplevel:true
[Symbol symbol; Symbol_field (symbol,i)] field)
fields;
loop program
| Effect (expr, program) ->
mark_loop ~toplevel:true [] expr;
loop program
| Let_symbol (_, def, program) ->
mark_constant_defining_value def;
loop program
| Let_rec_symbol (defs, program) ->
List.iter (fun (_, def) -> mark_constant_defining_value def) defs;
loop program
in
loop program.program_body
let res =
mark_program program;
{ id = variables;
closure = closures;
}
end
let inconstants_on_program ~compilation_unit ~backend
(program : Flambda.program) =
let module P = struct
let program = program
let compilation_unit = compilation_unit
end in
let module Backend = (val backend : Backend_intf.S) in
let module I = Inconstants (P) (Backend) in
I.res
let variable var { id; _ } =
match Variable.Tbl.find id var with
| Not_constant -> true
| Implication _ -> false
| exception Not_found -> false
let closure cl { closure; _ } =
match Set_of_closures_id.Tbl.find closure cl with
| Not_constant -> true
| Implication _ -> false
| exception Not_found -> false
|