Skip to content

Commit fa951ba

Browse files
committed
Compiler: Optimize stack resuming in the js runtime, fix 1658
1 parent bff8d13 commit fa951ba

File tree

15 files changed

+305
-153
lines changed

15 files changed

+305
-153
lines changed

.github/workflows/build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ jobs:
5757
skip-effects: false
5858
skip-test: false
5959
skip-doc: false
60+
- os: ubuntu-latest
61+
ocaml-compiler: "5.1"
62+
skip-effects: false
63+
skip-test: false
64+
skip-doc: true
6065
# Note this OCaml compiler is bytecode only
6166
- os: ubuntu-latest
6267
ocaml-compiler: "ocaml-variants.5.2.0+options,ocaml-option-32bit"

CHANGES.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
* Merged Wasm_of_ocaml (#1724)
77
* Lib: removed no longer relevant Js.optdef type annotations (#1769)
88
* Misc: drop support for IE
9-
* Effects: add an optional feature of "dynamic switching" between CPS and direct style, resulting in better performance when no effect handler is installed
9+
* Effects: add an optional feature of "dynamic switching" between CPS
10+
and direct style, resulting in better performance when
11+
no effect handler is installed
12+
* Compiler/Runtime: Make resuming a continuation more efficient in js (#1765)
1013

1114
## Bug fixes
1215
* Fix small bug in global data flow analysis (#1768)

compiler/lib/effects.ml

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -613,11 +613,34 @@ let cps_block ~st ~k ~orig_pc block =
613613
in
614614

615615
let rewrite_last_instr (x : Var.t) (e : expr) : (k:Var.t -> instr list * last) option =
616-
let perform_effect ~effect_ ~continuation =
616+
let perform_effect ~effect_ continuation_and_tail =
617617
Some
618618
(fun ~k ->
619619
let e =
620-
Prim (Extern "caml_perform_effect", [ Pv effect_; continuation; Pv k ])
620+
match Config.target () with
621+
| `JavaScript -> (
622+
match continuation_and_tail with
623+
| None -> Prim (Extern "caml_perform_effect", [ Pv effect_; Pv k ])
624+
| Some (continuation, tail) ->
625+
Prim
626+
( Extern "caml_reperform_effect"
627+
, [ Pv effect_; continuation; tail; Pv k ] ))
628+
| `Wasm -> (
629+
(* temporary until we finish the change to the wasmoo
630+
runtime *)
631+
match continuation_and_tail with
632+
| None ->
633+
Prim
634+
( Extern "caml_perform_effect"
635+
, [ Pv effect_
636+
; Pc (Int Targetint.zero)
637+
; Pc (Int Targetint.zero)
638+
; Pv k
639+
] )
640+
| Some (continuation, tail) ->
641+
Prim
642+
( Extern "caml_perform_effect"
643+
, [ Pv effect_; continuation; tail; Pv k ] ))
621644
in
622645
let x = Var.fresh () in
623646
[ Let (x, e) ], Return x)
@@ -628,22 +651,22 @@ let cps_block ~st ~k ~orig_pc block =
628651
(fun ~k ->
629652
let exact = exact || call_exact st.flow_info f (List.length args) in
630653
tail_call ~st ~exact ~in_cps:true ~check:true ~f (args @ [ k ]))
631-
| Prim (Extern "%resume", [ Pv stack; Pv f; Pv arg ]) ->
654+
| Prim (Extern "%resume", [ Pv stack; Pv f; Pv arg; tail ]) ->
632655
Some
633656
(fun ~k ->
634657
let k' = Var.fresh_n "cont" in
635658
tail_call
636659
~st
637-
~instrs:[ Let (k', Prim (Extern "caml_resume_stack", [ Pv stack; Pv k ])) ]
660+
~instrs:
661+
[ Let (k', Prim (Extern "caml_resume_stack", [ Pv stack; tail; Pv k ])) ]
638662
~exact:(call_exact st.flow_info f 1)
639663
~in_cps:true
640664
~check:true
641665
~f
642666
[ arg; k' ])
643-
| Prim (Extern "%perform", [ Pv effect_ ]) ->
644-
perform_effect ~effect_ ~continuation:(Pc (Int Targetint.zero))
645-
| Prim (Extern "%reperform", [ Pv effect_; continuation ]) ->
646-
perform_effect ~effect_ ~continuation
667+
| Prim (Extern "%perform", [ Pv effect_ ]) -> perform_effect ~effect_ None
668+
| Prim (Extern "%reperform", [ Pv effect_; continuation; tail ]) ->
669+
perform_effect ~effect_ (Some (continuation, tail))
647670
| _ -> None
648671
in
649672

@@ -712,12 +735,12 @@ let rewrite_direct_block ~st ~cps_needed ~closure_info ~pc block =
712735
; Let (cps_c, Closure (cps_params, cps_cont))
713736
; Let (x, Prim (Extern "caml_cps_closure", [ Pv direct_c; Pv cps_c ]))
714737
]
715-
| Let (x, Prim (Extern "%resume", [ Pv stack; Pv f; Pv arg ])) ->
716-
[ Let (x, Prim (Extern "caml_resume", [ Pv f; Pv arg; Pv stack ])) ]
738+
| Let (x, Prim (Extern "%resume", [ Pv stack; Pv f; Pv arg; Pv tail ])) ->
739+
[ Let (x, Prim (Extern "caml_resume", [ Pv f; Pv arg; Pv stack; Pv tail ])) ]
717740
| Let (x, Prim (Extern "%perform", [ Pv effect_ ])) ->
718741
(* In direct-style code, we just raise [Effect.Unhandled]. *)
719742
[ Let (x, Prim (Extern "caml_raise_unhandled", [ Pv effect_ ])) ]
720-
| Let (x, Prim (Extern "%reperform", [ Pv effect_; Pv _continuation ])) ->
743+
| Let (x, Prim (Extern "%reperform", [ Pv effect_; Pv _continuation; Pv _tail ])) ->
721744
(* Similar to previous case *)
722745
[ Let (x, Prim (Extern "caml_raise_unhandled", [ Pv effect_ ])) ]
723746
| Let (x, Prim (Extern "caml_assume_no_perform", [ Pv f ])) ->

compiler/lib/parse_bytecode.ml

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2368,7 +2368,6 @@ and compile infos pc state (instrs : instr list) =
23682368
let func = State.peek 0 state in
23692369
let arg = State.peek 1 state in
23702370
let x, state = State.fresh_var state in
2371-
23722371
if debug_parser ()
23732372
then
23742373
Format.printf
@@ -2381,23 +2380,30 @@ and compile infos pc state (instrs : instr list) =
23812380
func
23822381
Var.print
23832382
arg;
2384-
let state =
2383+
let state, tail =
23852384
match Ocaml_version.compare Ocaml_version.current [ 5; 2 ] < 0 with
2386-
| true -> State.pop 2 state
2387-
| false -> State.pop 3 state
2385+
| true -> State.pop 2 state, Pc (Int (Targetint.of_int_exn 0))
2386+
| false ->
2387+
let tail = State.peek 2 state in
2388+
State.pop 3 state, Pv tail
23882389
in
2389-
23902390
compile
23912391
infos
23922392
(pc + 1)
23932393
state
2394-
(Let (x, Prim (Extern "%resume", [ Pv stack; Pv func; Pv arg ])) :: instrs)
2394+
(Let (x, Prim (Extern "%resume", [ Pv stack; Pv func; Pv arg; tail ])) :: instrs)
23952395
| RESUMETERM ->
23962396
let stack = State.accu state in
23972397
let func = State.peek 0 state in
23982398
let arg = State.peek 1 state in
23992399
let x, state = State.fresh_var state in
2400-
2400+
let tail =
2401+
match Ocaml_version.compare Ocaml_version.current [ 5; 2 ] < 0 with
2402+
| true -> Pc (Int (Targetint.of_int_exn 0))
2403+
| false ->
2404+
let tail = State.peek 2 state in
2405+
Pv tail
2406+
in
24012407
if debug_parser ()
24022408
then
24032409
Format.printf
@@ -2408,7 +2414,7 @@ and compile infos pc state (instrs : instr list) =
24082414
func
24092415
Var.print
24102416
arg;
2411-
( Let (x, Prim (Extern "%resume", [ Pv stack; Pv func; Pv arg ])) :: instrs
2417+
( Let (x, Prim (Extern "%resume", [ Pv stack; Pv func; Pv arg; tail ])) :: instrs
24122418
, Return x
24132419
, state )
24142420
| PERFORM ->
@@ -2425,13 +2431,13 @@ and compile infos pc state (instrs : instr list) =
24252431
| REPERFORMTERM ->
24262432
let eff = State.accu state in
24272433
let stack = State.peek 0 state in
2428-
(* We don't need [State.peek 1 state] *)
2434+
let tail = State.peek 1 state in
24292435
let state = State.pop 2 state in
24302436
let x, state = State.fresh_var state in
24312437

24322438
if debug_parser ()
24332439
then Format.printf "return reperform(%a, %a)@." Var.print eff Var.print stack;
2434-
( Let (x, Prim (Extern "%reperform", [ Pv eff; Pv stack ])) :: instrs
2440+
( Let (x, Prim (Extern "%reperform", [ Pv eff; Pv stack; Pv tail ])) :: instrs
24352441
, Return x
24362442
, state )
24372443
| EVENT | BREAK | FIRST_UNIMPLEMENTED_OP -> assert false)

compiler/tests-check-prim/main.4.14.output

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@ caml_bigstring_blit_string_to_ba
3535
caml_bigstring_memcmp
3636
caml_hash_mix_bigstring
3737

38-
From +effect.js:
39-
jsoo_effect_not_supported
40-
4138
From +fs.js:
4239
caml_ba_map_file
4340
caml_ba_map_file_bytecode

compiler/tests-check-prim/unix-Unix.4.14.output

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,6 @@ caml_bigstring_blit_string_to_ba
144144
caml_bigstring_memcmp
145145
caml_hash_mix_bigstring
146146

147-
From +effect.js:
148-
jsoo_effect_not_supported
149-
150147
From +fs.js:
151148
caml_ba_map_file
152149
caml_ba_map_file_bytecode

compiler/tests-check-prim/unix-Win32.4.14.output

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,6 @@ caml_bigstring_blit_string_to_ba
109109
caml_bigstring_memcmp
110110
caml_hash_mix_bigstring
111111

112-
From +effect.js:
113-
jsoo_effect_not_supported
114-
115112
From +fs.js:
116113
caml_ba_map_file
117114
caml_ba_map_file_bytecode

compiler/tests-full/stdlib.cma.expected.js

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36136,8 +36136,8 @@
3613636136
/*<<effect.ml:28:11>>*/ return 0;
3613736137
var
3613836138
x = /*<<effect.ml:22:16>>*/ param[2],
36139-
_i_ = /*<<effect.ml:25:12>>*/ caml_call1(Stdlib_Printexc[26], x),
36140-
msg = /*<<effect.ml:24:18>>*/ caml_call2(Stdlib_Printf[4], _a_, _i_);
36139+
_o_ = /*<<effect.ml:25:12>>*/ caml_call1(Stdlib_Printexc[26], x),
36140+
msg = /*<<effect.ml:24:18>>*/ caml_call2(Stdlib_Printf[4], _a_, _o_);
3614136141
/*<<effect.ml:27:8>>*/ return [0, msg];
3614236142
/*<<effect.ml:28:15>>*/ }
3614336143
/*<<effect.ml:30:2>>*/ caml_call1(Stdlib_Printexc[9], printer);
@@ -36155,22 +36155,28 @@
3615536155
"Effect.Continuation_already_resumed",
3615636156
Continuation_already_resumed);
3615736157
function continue$0(k, v){
36158-
var _h_ = /*<<effect.ml:62:11>>*/ caml_continuation_use_noexc(k);
36159-
function _g_(x){
36158+
var
36159+
_l_ = /*<<effect.ml:62:11>>*/ k[2],
36160+
_n_ = caml_continuation_use_noexc(k);
36161+
function _m_(x){
3616036162
/*<<effect.ml:62:41>>*/ return x;
3616136163
/*<<effect.ml:62:42>>*/ }
3616236164
/*<<effect.ml:62:30>>*/ return jsoo_effect_not_supported() /*<<effect.ml:62:65>>*/ ;
3616336165
}
3616436166
function discontinue(k, e){
36165-
var _f_ = /*<<effect.ml:65:11>>*/ caml_continuation_use_noexc(k);
36166-
function _e_(e){
36167+
var
36168+
_i_ = /*<<effect.ml:65:11>>*/ k[2],
36169+
_k_ = caml_continuation_use_noexc(k);
36170+
function _j_(e){
3616736171
/*<<effect.ml:65:41>>*/ throw caml_maybe_attach_backtrace(e, 1);
3616836172
/*<<effect.ml:65:48>>*/ }
3616936173
/*<<effect.ml:65:30>>*/ return jsoo_effect_not_supported() /*<<effect.ml:65:71>>*/ ;
3617036174
}
3617136175
function discontinue_with_backtrace(k, e, bt){
36172-
var _d_ = /*<<effect.ml:68:11>>*/ caml_continuation_use_noexc(k);
36173-
function _c_(e){
36176+
var
36177+
_f_ = /*<<effect.ml:68:11>>*/ k[2],
36178+
_h_ = caml_continuation_use_noexc(k);
36179+
function _g_(e){
3617436180
/*<<effect.ml:68:41>>*/ caml_restore_raw_backtrace(e, bt);
3617536181
throw caml_maybe_attach_backtrace(e, 0);
3617636182
/*<<effect.ml:68:75>>*/ }
@@ -36187,8 +36193,9 @@
3618736193
}
3618836194
var
3618936195
s =
36190-
/*<<effect.ml:87:12>>*/ caml_alloc_stack(handler[1], handler[2], effc);
36191-
/*<<effect.ml:88:4>>*/ return jsoo_effect_not_supported() /*<<effect.ml:88:23>>*/ ;
36196+
/*<<effect.ml:87:12>>*/ caml_alloc_stack(handler[1], handler[2], effc),
36197+
_e_ = /*<<effect.ml:88:4>>*/ 0;
36198+
return jsoo_effect_not_supported() /*<<effect.ml:88:23>>*/ ;
3619236199
}
3619336200
function try_with(comp, arg, handler){
3619436201
function effc(eff, k, last_fiber){
@@ -36208,8 +36215,9 @@
3620836215
function(e){
3620936216
/*<<effect.ml:101:47>>*/ throw caml_maybe_attach_backtrace(e, 1);
3621036217
/*<<effect.ml:101:54>>*/ },
36211-
effc);
36212-
/*<<effect.ml:102:4>>*/ return jsoo_effect_not_supported() /*<<effect.ml:102:23>>*/ ;
36218+
effc),
36219+
_d_ = /*<<effect.ml:102:4>>*/ 0;
36220+
return jsoo_effect_not_supported() /*<<effect.ml:102:23>>*/ ;
3621336221
}
3621436222
var
3621536223
Deep =
@@ -36243,7 +36251,7 @@
3624336251
var s = /*<<effect.ml:135:12>>*/ caml_alloc_stack(error, error, effc);
3624436252
/*<<effect.ml:136:4>>*/ try{
3624536253
/*<<effect.ml:136:10>>*/ jsoo_effect_not_supported();
36246-
var _b_ = /*<<effect.ml:136:26>>*/ 0;
36254+
var _b_ = /*<<effect.ml:136:26>>*/ 0, _c_ = 0;
3624736255
}
3624836256
catch(exn$0){
3624936257
var exn = /*<<?>>*/ caml_wrap_exception(exn$0);
@@ -36263,6 +36271,7 @@
3626336271
/*<<effect.ml:160:10>>*/ return caml_call1(f, k) /*<<effect.ml:161:42>>*/ ;
3626436272
}
3626536273
var
36274+
last_fiber = /*<<effect.ml:163:4>>*/ k[2],
3626636275
stack =
3626736276
/*<<effect.ml:164:16>>*/ runtime.caml_continuation_use_and_update_handler_noexc
3626836277
(k, handler[1], handler[2], effc);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
(* deep_state.ml *)
2+
3+
open Effect
4+
open Effect.Shallow
5+
6+
module type State = sig
7+
type a
8+
9+
type _ Effect.t += Get : a Effect.t
10+
11+
type _ Effect.t += Set : a -> unit Effect.t
12+
end
13+
14+
module Make (S : State) = struct
15+
let rec loop : type x y. S.a -> (x, y) continuation -> x -> y =
16+
fun s k x ->
17+
continue_with
18+
k
19+
x
20+
{ retc = (fun y -> y)
21+
; exnc = raise
22+
; effc =
23+
(fun (type b) (e : b Effect.t) ->
24+
match e with
25+
| S.Get -> Some (fun (k : (b, _) continuation) -> loop s k s)
26+
| S.Set s -> Some (fun (k : (b, _) continuation) -> loop s k ())
27+
| _ -> None)
28+
}
29+
30+
let handle (s : S.a) (f : unit -> 'a) : 'a = loop s (fiber f) ()
31+
32+
let get () = perform S.Get
33+
34+
let set v = perform (S.Set v)
35+
end
36+
37+
module IntState = struct
38+
type a = int
39+
40+
type _ Effect.t += Get : int Effect.t
41+
42+
type _ Effect.t += Set : int -> unit Effect.t
43+
end
44+
45+
module StringState = struct
46+
type a = string
47+
48+
type _ Effect.t += Get : string Effect.t
49+
50+
type _ Effect.t += Set : string -> unit Effect.t
51+
end
52+
53+
let main () =
54+
let depth = int_of_string Sys.argv.(1) in
55+
let ops = int_of_string Sys.argv.(2) in
56+
Printf.printf "Running deepstate: depth=%d ops=%d\n" depth ops;
57+
let module SS = Make (StringState) in
58+
let rec setup_deep_state n () =
59+
if n = 0
60+
then
61+
for _ = 1 to ops do
62+
(* SS.set (SS.get () ^ "_" ^ (string_of_int i)) *)
63+
SS.set (SS.get ())
64+
done
65+
(* print_endline @@ SS.get() *)
66+
else
67+
let module IS = Make (IntState) in
68+
IS.handle 0 @@ setup_deep_state (n - 1)
69+
in
70+
71+
SS.handle "Hello, world!" @@ setup_deep_state depth
72+
73+
let _ = main ()

compiler/tests-jsoo/lib-effects/dune

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
\
1616
assume_no_perform
1717
assume_no_perform_unhandled
18-
assume_no_perform_nested_handler))
18+
assume_no_perform_nested_handler
19+
deep_state ))
1920
(preprocess
2021
(pps ppx_expect)))
2122

@@ -37,3 +38,10 @@
3738
0
3839
(run node %{test}))))
3940
(modes js wasm))
41+
42+
(executable
43+
(name deep_state)
44+
(enabled_if
45+
(>= %{ocaml_version} 5))
46+
(modules deep_state)
47+
(modes js))

0 commit comments

Comments
 (0)