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
(** Cascade-aware rule factoring. *)
open Stylesheet
let counters = Stats.counters
let rules_pp_size = Size.rules
let list_map_preserve = Common.List.map_preserve
type cache = (string * rule list, rule list) Hashtbl.t
let cache () = Hashtbl.create 16
let ctx_key ctx =
let b x = if x then "1" else "0" in
String.concat ""
[
(match Ctx.scope ctx with `Fragment -> "f" | `Stylesheet -> "s");
b (Ctx.lossless ctx);
b (Ctx.aggressive ctx);
b (Ctx.extend_lists ctx);
b (Ctx.closed_world ctx);
(match Ctx.objective ctx with `Raw -> "r" | `Transfer -> "t");
]
let cache_key ~ctx rules = (ctx_key ctx, rules)
let order_is_original rules graph order =
Rule_graph.generation graph = 0
&& Array.length order = List.length rules
&&
let ok = ref true in
for i = 0 to Array.length order - 1 do
if Rule_graph.Node_id.to_int order.(i) <> i then ok := false
done;
!ok
let ordered_rules rules graph =
let order = Rule_graph.canonical_order graph in
if order_is_original rules graph order then rules
else Array.to_list (Array.map (Rule_graph.node_rule graph) order)
let should_run_preflight ~ctx summary =
if Preflight.declaration_count summary > Preflight.small_declaration_threshold
then
counters.factor_preflight_gain <-
counters.factor_preflight_gain + Preflight.estimated_gain summary;
Preflight.useful summary || Ctx.aggressive ctx
let record_iteration ~fixpoint ~local_iteration ~before_rules ~before_bytes
~after_rules ~after_bytes ~bytes_saved ~changed ~elapsed =
Stats.record_iteration ~fixpoint ~local_iteration ~before_rules ~before_bytes
~after_rules ~after_bytes ~bytes_saved ~active_passes:1
~changed_passes:(if changed then 1 else 0)
~elapsed
let optimize_graph ~ctx ~finalize ~fixpoint ~local_iteration rules graph =
counters.iterations <- counters.iterations + 1;
Stats.reset_saving ();
let before_rules = List.length rules in
let before_bytes = if Stats.profile () then rules_pp_size rules else 0 in
let started_at = Unix.gettimeofday () in
let graph = Rule_scheduler.run ~ctx ~finalize graph in
let ordered = ordered_rules rules graph in
let rules' = list_map_preserve finalize ordered in
let after_bytes = if Stats.profile () then rules_pp_size rules' else 0 in
let elapsed = Unix.gettimeofday () -. started_at in
let bytes_saved = Stats.saving () in
let changed = rules' != rules in
record_iteration ~fixpoint ~local_iteration ~before_rules ~before_bytes
~after_rules:(List.length rules') ~after_bytes ~bytes_saved ~changed
~elapsed;
if changed then rules' else rules
let transfer_gate_min_bytes = 4096
let transfer_gate_margin before = max 16 (before / 100)
let render_rules rules =
Pp.to_string ~minify:true (fun ctx -> List.iter (pp_rule ctx)) rules
let factored_grows_transfer ~ctx ~unfactored ~factored =
Ctx.objective ctx = `Transfer
&&
let before = render_rules unfactored in
String.length before >= transfer_gate_min_bytes
&&
let before_gz = Gzip_size.estimate before in
Gzip_size.estimate (render_rules factored)
> before_gz + transfer_gate_margin before_gz
let run_segment ?cache ~ctx ~finalize (rules : rule list) =
let key = Option.map (fun _ -> cache_key ~ctx rules) cache in
match
match (cache, key) with
| Some cache, Some key -> Hashtbl.find_opt cache key
| _ -> None
with
| Some rules -> rules
| None ->
let summary = Preflight.summarize rules in
let graph =
Rule_graph.of_rules ~closed_world:(Ctx.closed_world ctx) rules
in
let result =
if not (should_run_preflight ~ctx summary) then begin
counters.factor_fixpoints_skipped <-
counters.factor_fixpoints_skipped + 1;
ordered_rules rules graph
end
else begin
counters.factor_fixpoints_run <- counters.factor_fixpoints_run + 1;
let fixpoint = counters.factor_fixpoints_run in
let unfactored = ordered_rules rules graph in
let factored =
optimize_graph ~ctx ~finalize ~fixpoint ~local_iteration:1 rules
graph
in
if
factored != rules
&& factored_grows_transfer ~ctx ~unfactored ~factored
then begin
counters.factor_transfer_reverts <-
counters.factor_transfer_reverts + 1;
unfactored
end
else factored
end
in
(match (cache, key) with
| Some cache, Some key -> Hashtbl.replace cache key result
| _ -> ());
result
let run ?cache ~ctx ~finalize (rules : rule list) =
run_segment ?cache ~ctx ~finalize rules