Skip to content

Commit e0bfae3

Browse files
committed
Reference unboxing
1 parent f9b616b commit e0bfae3

File tree

8 files changed

+1358
-1117
lines changed

8 files changed

+1358
-1117
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Features/Changes
44
* Compiler/wasm: omit code pointer from closures when not used (#2059, #2093)
55
* Compiler/wasm: unbox numbers within functions (#2069)
6+
* Compiler: reference unboxing (#1958)
67

78
## Bug fixes
89
* Compiler: fix purity of comparison functions (again) (#2092)

compiler/lib/driver.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ let round profile : 'a -> 'a =
217217
print
218218
+> tailcall
219219
+> (flow +> specialize +> eval +> fst)
220+
+> Ref_unboxing.f
220221
+> inline profile
221222
+> phi
222223
+> deadcode

compiler/lib/phisimpl.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ let program_deps { blocks; _ } =
6969
(fun _pc block ->
7070
List.iter block.body ~f:(fun i ->
7171
match i with
72+
| Let (x, Prim (Extern "%identity", [ Pv y ])) ->
73+
(* This is introduced by the reference unboxing pass *)
74+
add_var vars x;
75+
add_dep deps x y;
76+
add_def vars defs x y
7277
| Let (x, e) ->
7378
add_var vars x;
7479
expr_deps blocks vars deps defs x e

compiler/lib/ref_unboxing.ml

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
open! Stdlib
2+
open Code
3+
4+
(*
5+
ocamlc does not perform reference unboxing when emitting debugging
6+
information. Inlining can also enable additional reference unboxing.
7+
8+
We currently does not unbox references which are used within the scope
9+
of an exception handler. This should often not result in significant
10+
performance improvements, and is tricky to get right. Indeed, we would
11+
need to introduce variables for these references right before the
12+
[Pushtrap], and then add [Assign] instructions to keep their contents
13+
up to date whenever a reference is updated.
14+
*)
15+
16+
let debug = Debug.find "unbox-refs"
17+
18+
let times = Debug.find "times"
19+
20+
let stats = Debug.find "stats"
21+
22+
let rewrite refs block m =
23+
let m, l =
24+
List.fold_left
25+
~f:(fun (m, rem) i ->
26+
match i with
27+
| Let (x, Block (0, [| y |], (NotArray | Unknown), Maybe_mutable))
28+
when Var.Set.mem x refs -> Var.Map.add x y m, rem
29+
| Let (y, Field (x, 0, Non_float)) when Var.Map.mem x m ->
30+
(* Optimized away by Phisimpl *)
31+
m, Let (y, Prim (Extern "%identity", [ Pv (Var.Map.find x m) ])) :: rem
32+
| Offset_ref (x, n) when Var.Map.mem x m ->
33+
let y = Var.fresh () in
34+
( Var.Map.add x y m
35+
, Let
36+
( y
37+
, Prim
38+
( Extern "%int_add"
39+
, [ Pv (Var.Map.find x m); Pc (Int (Targetint.of_int_exn n)) ] ) )
40+
:: rem )
41+
| Set_field (x, _, Non_float, y) when Var.Map.mem x m -> Var.Map.add x y m, rem
42+
| Event _
43+
when match rem with
44+
| Event _ :: _ -> true
45+
| _ -> false -> m, rem
46+
| _ -> m, i :: rem)
47+
block.body
48+
~init:(m, [])
49+
in
50+
m, List.rev l
51+
52+
let rewrite_cont relevant_vars vars (pc', args) =
53+
let refs, _ = Int.Hashtbl.find relevant_vars pc' in
54+
let vars = Var.Map.filter (fun x _ -> Var.Set.mem x refs) vars in
55+
pc', List.map ~f:snd (Var.Map.bindings vars) @ args
56+
57+
let rewrite_function p variables pc =
58+
let relevant_vars = Int.Hashtbl.create 16 in
59+
let g = Structure.(dominator_tree (build_graph p.blocks pc)) in
60+
let rec traverse_tree g pc vars =
61+
let block = Addr.Map.find pc p.blocks in
62+
let vars' =
63+
List.fold_left
64+
~f:(fun s i ->
65+
match i with
66+
| Let (x, Block (0, [| _ |], (NotArray | Unknown), Maybe_mutable))
67+
when Var.Hashtbl.mem variables x -> Var.Set.add x s
68+
| _ -> s)
69+
~init:vars
70+
block.body
71+
in
72+
Int.Hashtbl.add relevant_vars pc (vars, vars');
73+
Addr.Set.iter (fun pc' -> traverse_tree g pc' vars') (Structure.get_edges g pc)
74+
in
75+
traverse_tree g pc Var.Set.empty;
76+
let rec traverse_tree' g pc blocks =
77+
let block = Addr.Map.find pc p.blocks in
78+
let vars, refs = Int.Hashtbl.find relevant_vars pc in
79+
let vars =
80+
Var.Set.fold (fun x m -> Var.Map.add x (Var.fork x) m) vars Var.Map.empty
81+
in
82+
let params = List.map ~f:snd (Var.Map.bindings vars) @ block.params in
83+
let vars, body = rewrite refs block vars in
84+
let branch =
85+
match block.branch with
86+
| Return _ | Raise _ | Stop -> block.branch
87+
| Branch cont -> Branch (rewrite_cont relevant_vars vars cont)
88+
| Cond (x, cont, cont') ->
89+
Cond
90+
( x
91+
, rewrite_cont relevant_vars vars cont
92+
, rewrite_cont relevant_vars vars cont' )
93+
| Switch (x, a) ->
94+
Switch (x, Array.map ~f:(fun cont -> rewrite_cont relevant_vars vars cont) a)
95+
| Pushtrap (cont, x, cont') ->
96+
Pushtrap
97+
( rewrite_cont relevant_vars vars cont
98+
, x
99+
, rewrite_cont relevant_vars vars cont' )
100+
| Poptrap cont -> Poptrap (rewrite_cont relevant_vars vars cont)
101+
in
102+
let blocks = Addr.Map.add pc { params; body; branch } blocks in
103+
Addr.Set.fold
104+
(fun pc' blocks -> traverse_tree' g pc' blocks)
105+
(Structure.get_edges g pc)
106+
blocks
107+
in
108+
let blocks = traverse_tree' g pc p.blocks in
109+
{ p with blocks }
110+
111+
let f p =
112+
let t = Timer.make () in
113+
let candidates = Var.Hashtbl.create 128 in
114+
let updated = Var.Hashtbl.create 128 in
115+
let visited = BitSet.create' p.free_pc in
116+
let discard x = Var.Hashtbl.remove candidates x in
117+
let check_field_access depth x =
118+
match Var.Hashtbl.find candidates x with
119+
| exception Not_found -> false
120+
| depth' ->
121+
if depth' = depth
122+
then true
123+
else (
124+
Var.Hashtbl.remove candidates x;
125+
false)
126+
in
127+
let rec traverse depth start_pc pc =
128+
if not (BitSet.mem visited pc)
129+
then (
130+
BitSet.set visited pc;
131+
let block = Addr.Map.find pc p.blocks in
132+
List.iter
133+
~f:(fun i ->
134+
match i with
135+
| Let (x, Block (0, [| _ |], (NotArray | Unknown), Maybe_mutable)) ->
136+
Freevars.iter_instr_free_vars discard i;
137+
Var.Hashtbl.replace candidates x depth
138+
| Let (_, Closure (_, (pc', _), _)) -> traverse (depth + 1) pc' pc'
139+
| Let (_, Field (x, 0, Non_float)) -> ignore (check_field_access depth x)
140+
| Offset_ref (x, _) ->
141+
if check_field_access depth x then Var.Hashtbl.replace updated x start_pc
142+
| Set_field (x, _, Non_float, y) ->
143+
discard y;
144+
if check_field_access depth x then Var.Hashtbl.replace updated x start_pc
145+
| _ -> Freevars.iter_instr_free_vars discard i)
146+
block.body;
147+
Freevars.iter_last_free_var discard block.branch;
148+
match block.branch with
149+
| Pushtrap ((pc', _), _, (pc'', _)) ->
150+
traverse (depth + 1) start_pc pc';
151+
traverse depth start_pc pc''
152+
| Poptrap (pc', _) -> traverse (depth - 1) start_pc pc'
153+
| _ -> Code.fold_children p.blocks pc (fun pc' () -> traverse depth start_pc pc') ())
154+
in
155+
traverse 0 p.start p.start;
156+
if debug ()
157+
then
158+
Print.program
159+
Format.err_formatter
160+
(fun _ i ->
161+
match i with
162+
| Instr (Let (x, _))
163+
when Var.Hashtbl.mem candidates x && Var.Hashtbl.mem updated x -> "REF"
164+
| _ -> "")
165+
p;
166+
Var.Hashtbl.filter_map_inplace
167+
(fun x _depth -> try Some (Var.Hashtbl.find updated x) with Not_found -> None)
168+
candidates;
169+
let functions =
170+
Var.Hashtbl.fold (fun _ pc s -> Addr.Set.add pc s) candidates Addr.Set.empty
171+
in
172+
let p = Addr.Set.fold (fun pc p -> rewrite_function p candidates pc) functions p in
173+
if times () then Format.eprintf " reference unboxing: %a@." Timer.print t;
174+
if stats ()
175+
then Format.eprintf "Stats - reference unboxing: %d@." (Var.Hashtbl.length candidates);
176+
p

compiler/tests-compiler/double-translation/effects_continuations.ml

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
101101
[%expect
102102
{|
103103
function exceptions$0(s){
104-
try{var _k_ = caml_int_of_string(s), n = _k_;}
104+
try{var _l_ = caml_int_of_string(s), n = _l_;}
105105
catch(exn$0){
106106
var exn = caml_wrap_exception(exn$0), tag = exn[1];
107107
if(tag !== Stdlib[7]) throw caml_maybe_attach_backtrace(exn, 0);
@@ -110,7 +110,7 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
110110
try{
111111
if(caml_string_equal(s, cst$0))
112112
throw caml_maybe_attach_backtrace(Stdlib[8], 1);
113-
var _j_ = 7, m = _j_;
113+
var _k_ = 7, m = _k_;
114114
}
115115
catch(exn){
116116
var exn$0 = caml_wrap_exception(exn);
@@ -120,8 +120,8 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
120120
try{
121121
if(caml_string_equal(s, cst))
122122
throw caml_maybe_attach_backtrace(Stdlib[8], 1);
123-
var _i_ = [0, [0, caml_call1(Stdlib[79], cst_toto), n, m]];
124-
return _i_;
123+
var _j_ = [0, [0, caml_call1(Stdlib[79], cst_toto), n, m]];
124+
return _j_;
125125
}
126126
catch(exn){
127127
var exn$1 = caml_wrap_exception(exn);
@@ -131,7 +131,7 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
131131
}
132132
//end
133133
function exceptions$1(s, cont){
134-
try{var _i_ = caml_int_of_string(s), n = _i_;}
134+
try{var _j_ = caml_int_of_string(s), n = _j_;}
135135
catch(exn){
136136
var exn$2 = caml_wrap_exception(exn), tag = exn$2[1];
137137
if(tag !== Stdlib[7]){
@@ -145,7 +145,7 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
145145
try{
146146
if(caml_string_equal(s, cst$0))
147147
throw caml_maybe_attach_backtrace(Stdlib[8], 1);
148-
var _h_ = 7, m = _h_;
148+
var _i_ = 7, m = _i_;
149149
}
150150
catch(exn$0){
151151
var exn$1 = caml_wrap_exception(exn$0);
@@ -165,9 +165,9 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
165165
return caml_trampoline_cps_call2
166166
(Stdlib[79],
167167
cst_toto,
168-
function(_i_){caml_pop_trap(); return cont([0, [0, _i_, n, m]]);});
169-
var _g_ = Stdlib[8], raise = caml_pop_trap();
170-
return raise(caml_maybe_attach_backtrace(_g_, 1));
168+
function(_j_){caml_pop_trap(); return cont([0, [0, _j_, n, m]]);});
169+
var _h_ = Stdlib[8], raise = caml_pop_trap();
170+
return raise(caml_maybe_attach_backtrace(_h_, 1));
171171
}
172172
//end
173173
var exceptions = caml_cps_closure(exceptions$0, exceptions$1);
@@ -180,10 +180,10 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
180180
}
181181
//end
182182
function cond1$1(b, cont){
183-
function _g_(ic){return cont([0, ic, 7]);}
183+
function _h_(ic){return cont([0, ic, 7]);}
184184
return b
185-
? caml_trampoline_cps_call2(Stdlib[79], cst_toto$0, _g_)
186-
: caml_trampoline_cps_call2(Stdlib[79], cst_titi, _g_);
185+
? caml_trampoline_cps_call2(Stdlib[79], cst_toto$0, _h_)
186+
: caml_trampoline_cps_call2(Stdlib[79], cst_titi, _h_);
187187
}
188188
//end
189189
var cond1 = caml_cps_closure(cond1$0, cond1$1);
@@ -197,26 +197,26 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
197197
}
198198
//end
199199
function cond2$1(b, cont){
200-
function _g_(_g_){return cont(7);}
200+
function _h_(_h_){return cont(7);}
201201
return b
202-
? caml_trampoline_cps_call2(Stdlib_Printf[3], _a_, _g_)
203-
: caml_trampoline_cps_call2(Stdlib_Printf[3], _b_, _g_);
202+
? caml_trampoline_cps_call2(Stdlib_Printf[3], _a_, _h_)
203+
: caml_trampoline_cps_call2(Stdlib_Printf[3], _b_, _h_);
204204
}
205205
//end
206206
var cond2 = caml_cps_closure(cond2$0, cond2$1);
207207
//end
208208
function cond3$0(b){
209-
var x = [0, 0];
210-
if(b) x[1] = 1; else caml_call1(Stdlib_Printf[3], _c_);
211-
return x[1];
209+
var x = 0, x$0 = b ? 1 : (caml_call1(Stdlib_Printf[3], _c_), x);
210+
return x$0;
212211
}
213212
//end
214213
function cond3$1(b, cont){
215-
var x = [0, 0];
216-
function _g_(_g_){return cont(x[1]);}
214+
function _g_(x){return cont(x);}
215+
var x = 0;
217216
return b
218-
? (x[1] = 1, _g_(0))
219-
: caml_trampoline_cps_call2(Stdlib_Printf[3], _c_, _g_);
217+
? _g_(1)
218+
: caml_trampoline_cps_call2
219+
(Stdlib_Printf[3], _c_, function(_h_){return _g_(x);});
220220
}
221221
//end
222222
var cond3 = caml_cps_closure(cond3$0, cond3$1);

compiler/tests-compiler/effects_continuations.ml

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
102102
[%expect
103103
{|
104104
function exceptions(s, cont){
105-
try{var _i_ = runtime.caml_int_of_string(s), n = _i_;}
105+
try{var _j_ = runtime.caml_int_of_string(s), n = _j_;}
106106
catch(exn$0){
107107
var exn = caml_wrap_exception(exn$0), tag = exn[1];
108108
if(tag !== Stdlib[7]){
@@ -114,7 +114,7 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
114114
try{
115115
if(caml_string_equal(s, cst$0))
116116
throw caml_maybe_attach_backtrace(Stdlib[8], 1);
117-
var _h_ = 7, m = _h_;
117+
var _i_ = 7, m = _i_;
118118
}
119119
catch(exn){
120120
var exn$0 = caml_wrap_exception(exn);
@@ -136,31 +136,32 @@ let%expect_test "test-compiler/lib-effects/test1.ml" =
136136
return caml_trampoline_cps_call2
137137
(Stdlib[79],
138138
cst_toto,
139-
function(_i_){caml_pop_trap(); return cont([0, [0, _i_, n, m]]);});
140-
var _g_ = Stdlib[8], raise = caml_pop_trap();
141-
return raise(caml_maybe_attach_backtrace(_g_, 1));
139+
function(_j_){caml_pop_trap(); return cont([0, [0, _j_, n, m]]);});
140+
var _h_ = Stdlib[8], raise = caml_pop_trap();
141+
return raise(caml_maybe_attach_backtrace(_h_, 1));
142142
}
143143
//end
144144
function cond1(b, cont){
145-
function _g_(ic){return cont([0, ic, 7]);}
145+
function _h_(ic){return cont([0, ic, 7]);}
146146
return b
147-
? caml_trampoline_cps_call2(Stdlib[79], cst_toto$0, _g_)
148-
: caml_trampoline_cps_call2(Stdlib[79], cst_titi, _g_);
147+
? caml_trampoline_cps_call2(Stdlib[79], cst_toto$0, _h_)
148+
: caml_trampoline_cps_call2(Stdlib[79], cst_titi, _h_);
149149
}
150150
//end
151151
function cond2(b, cont){
152-
function _g_(_g_){return cont(7);}
152+
function _h_(_h_){return cont(7);}
153153
return b
154-
? caml_trampoline_cps_call2(Stdlib_Printf[3], _a_, _g_)
155-
: caml_trampoline_cps_call2(Stdlib_Printf[3], _b_, _g_);
154+
? caml_trampoline_cps_call2(Stdlib_Printf[3], _a_, _h_)
155+
: caml_trampoline_cps_call2(Stdlib_Printf[3], _b_, _h_);
156156
}
157157
//end
158158
function cond3(b, cont){
159-
var x = [0, 0];
160-
function _g_(_g_){return cont(x[1]);}
159+
function _g_(x){return cont(x);}
160+
var x = 0;
161161
return b
162-
? (x[1] = 1, _g_(0))
163-
: caml_trampoline_cps_call2(Stdlib_Printf[3], _c_, _g_);
162+
? _g_(1)
163+
: caml_trampoline_cps_call2
164+
(Stdlib_Printf[3], _c_, function(_h_){return _g_(x);});
164165
}
165166
//end
166167
function loop1(b, cont){

0 commit comments

Comments
 (0)