File: effect.js

package info (click to toggle)
js-of-ocaml 6.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 37,932 kB
  • sloc: ml: 135,957; javascript: 58,364; ansic: 437; makefile: 422; sh: 12; perl: 4
file content (363 lines) | stat: -rw-r--r-- 10,702 bytes parent folder | download
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
/*
To deal with effects, the execution context is intuitively composed of
a stack of fibers. Each fiber has a current low-level continuation
(one-argument JavaScript function), a stack of exception handlers and
a triple of handlers, which are invoked when the fiber terminates
(either with a value or an exception) or when an effect is performed.
The low-level continuation of the topmost fiber (which is currently
executing) is passed from function to function as an additional
argument. Its stack of exception handlers is stored in
[caml_current_stack.x].
Exception handlers are pushed into this stack
when entering a [try ... with ...] and popped on exit.
Handlers are stored in [caml_current_stack.h]
and the remaining fibers are stored in [caml_current_stack.e].
To install an effect handler, we push a new fiber into the execution context.

We have basically the following type for reified continuations (type
[continuation] in module [Effect] of the standard library):

  type ('a, 'b) continuation = ('a, 'b) stack ref

  and (_, _) stack =
      Cons : ('b -> unit) *             (* low-level continuation *)
             (exn -> unit) list *       (* exception handlers *)
             ('b, 'c) handler *
             ('a, 'b) stack
             -> ('a, 'c) stack
    | Empty : ('a, 'a) stack

  and ('a,'b) handler =   (* As in module Effect from the standard library *)
    { retc: 'a -> 'b;
      exnc: exn -> 'b;
      effc: 'c.'c Effect.t -> (('c,'b) continuation -> 'b) option }

Continuations are one-shot. A continuation [ref Empty] has already
been resumed.

A continuation is basically composed of a list of fibers, which each
has its low-level continuation, its stack of exception handlers and a
triple of handlers to deal with when the fiber terminates or an
effect is performed. When resuming a continuation, the innermost fiber
is resumed first.

The handlers are CPS-transformed functions: they actually take an
additional parameter which is the current low-level continuation.
*/

//Provides: caml_current_stack
//If: effects
// This has the shape {k, x, h, e} where
// - h is a triple of handlers (see effect.ml)
// - k is the low level continuation
// - x is the exception stack
// - e is the fiber stack of the parent fiber.
var caml_current_stack = { k: 0, x: 0, h: 0, e: 0 };

//Provides: caml_push_trap
//Requires: caml_current_stack
//If: effects
function caml_push_trap(handler) {
  caml_current_stack.x = { h: handler, t: caml_current_stack.x };
}

//Provides: caml_pop_trap
//Requires: caml_current_stack
//If: effects
function caml_pop_trap() {
  if (!caml_current_stack.x)
    return function (x) {
      throw x;
    };
  var h = caml_current_stack.x.h;
  caml_current_stack.x = caml_current_stack.x.t;
  return h;
}

//Provides: caml_raise_unhandled
//Requires: caml_make_unhandled_effect_exn
//If: effects
//Version: >= 5.0
function caml_raise_unhandled(eff) {
  var exn = caml_make_unhandled_effect_exn(eff);
  throw exn;
}

//Provides:caml_resume_stack
//Requires: caml_named_value, caml_raise_constant
//Requires: caml_pop_fiber, caml_current_stack
//If: effects
//Version: >= 5.0
function caml_resume_stack(stack, last, k) {
  if (!stack)
    caml_raise_constant(
      caml_named_value("Effect.Continuation_already_resumed"),
    );
  if (last === 0) {
    last = stack;
    // Pre OCaml 5.2, last was not populated.
    while (last.e !== 0) last = last.e;
  }
  caml_current_stack.k = k;
  last.e = caml_current_stack;
  caml_current_stack = stack;
  return stack.k;
}

//Provides: caml_pop_fiber
//Requires: caml_current_stack
//If: effects
//Version: >= 5.0
function caml_pop_fiber() {
  // Move to the parent fiber, returning the parent's low-level continuation
  var c = caml_current_stack.e;
  caml_current_stack.e = 0;
  caml_current_stack = c;
  return c.k;
}

//Provides: caml_make_unhandled_effect_exn
//Requires: caml_named_value, caml_string_of_jsbytes, caml_fresh_oo_id
//If: effects
//Version: >= 5.0
function caml_make_unhandled_effect_exn(eff) {
  var exn = caml_named_value("Effect.Unhandled");
  if (exn) exn = [0, exn, eff];
  else {
    exn = [
      248,
      caml_string_of_jsbytes("Effect.Unhandled"),
      caml_fresh_oo_id(0),
    ];
  }
  return exn;
}

//Provides: caml_perform_effect
//Requires: caml_pop_fiber, caml_stack_check_depth, caml_trampoline_return
//Requires: caml_make_unhandled_effect_exn, caml_current_stack
//Requires: caml_get_cps_fun
//If: effects
//Version: >= 5.0
function caml_perform_effect(eff, k0) {
  if (caml_current_stack.e === 0) {
    var exn = caml_make_unhandled_effect_exn(eff);
    throw exn;
  }
  // Get current effect handler
  var handler = caml_current_stack.h[3];
  var last_fiber = caml_current_stack;
  last_fiber.k = k0;
  var cont = [245 /*continuation*/, last_fiber, last_fiber];
  // Move to parent fiber and execute the effect handler there
  // The handler is defined in Stdlib.Effect, so we know that the arity matches
  var k1 = caml_pop_fiber();
  return caml_stack_check_depth()
    ? caml_get_cps_fun(handler)(eff, cont, last_fiber, k1)
    : caml_trampoline_return(handler, [eff, cont, last_fiber, k1]);
}

//Provides: caml_reperform_effect
//Requires: caml_pop_fiber, caml_stack_check_depth, caml_trampoline_return
//Requires: caml_make_unhandled_effect_exn, caml_current_stack
//Requires: caml_resume_stack, caml_continuation_use_noexc
//Requires: caml_get_cps_fun
//If: effects
//Version: >= 5.0
function caml_reperform_effect(eff, cont, last, k0) {
  if (caml_current_stack.e === 0) {
    var exn = caml_make_unhandled_effect_exn(eff);
    var stack = caml_continuation_use_noexc(cont);
    caml_resume_stack(stack, last, k0);
    throw exn;
  }
  // Get current effect handler
  var handler = caml_current_stack.h[3];
  var last_fiber = caml_current_stack;
  last_fiber.k = k0;
  last.e = last_fiber;
  cont[2] = last_fiber;
  // Move to parent fiber and execute the effect handler there
  // The handler is defined in Stdlib.Effect, so we know that the arity matches
  var k1 = caml_pop_fiber();
  return caml_stack_check_depth()
    ? caml_get_cps_fun(handler)(eff, cont, last_fiber, k1)
    : caml_trampoline_return(handler, [eff, cont, last_fiber, k1]);
}

//Provides: caml_get_cps_fun
//If: effects
//If: !doubletranslate
function caml_get_cps_fun(f) {
  return f;
}

//Provides: caml_get_cps_fun
//If: effects
//If: doubletranslate
function caml_get_cps_fun(f) {
  // This function is only used to get the effect handler. If the
  // effect handler has no CPS function, we know that we can directly
  // call the direct version instead.
  return f.cps ? f.cps : f;
}

//Provides: caml_alloc_stack
//Requires: caml_pop_fiber, caml_call_gen, caml_stack_check_depth, caml_trampoline_return
//Requires: caml_call_gen_cps, caml_current_stack
//If: effects
//Version: >= 5.0
function caml_alloc_stack_call(f, x) {
  var args = [x, caml_pop_fiber()];
  return caml_stack_check_depth()
    ? caml_call_gen_cps(f, args)
    : caml_trampoline_return(f, args, 0);
}
function caml_alloc_stack_hval(x) {
  // Call [hv] in the parent fiber
  var f = caml_current_stack.h[1];
  return caml_alloc_stack_call(f, x);
}
function caml_alloc_stack_hexn(e) {
  // Call [hx] in the parent fiber
  var f = caml_current_stack.h[2];
  return caml_alloc_stack_call(f, e);
}
function caml_alloc_stack(hv, hx, hf) {
  var handlers = [0, hv, hx, hf];
  return {
    k: caml_alloc_stack_hval,
    x: { h: caml_alloc_stack_hexn, t: 0 },
    h: handlers,
    e: 0,
  };
}

//Provides: caml_alloc_stack
//If: !effects
//Version: >= 5.0
function caml_alloc_stack(_hv, _hx, _hf) {
  return 0;
}

//Provides: caml_continuation_use_noexc
//Version: >= 5.0
function caml_continuation_use_noexc(cont) {
  var stack = cont[1];
  cont[1] = 0;
  return stack;
}

//Provides: caml_continuation_use_and_update_handler_noexc
//Requires: caml_continuation_use_noexc
//Version: >= 5.0
function caml_continuation_use_and_update_handler_noexc(
  cont,
  hval,
  hexn,
  heff,
) {
  var stack = caml_continuation_use_noexc(cont);
  if (stack === 0) return stack;
  var last = cont[2];
  last.h[1] = hval;
  last.h[2] = hexn;
  last.h[3] = heff;
  return stack;
}

//Provides: caml_get_continuation_callstack
//Version: >= 5.0
function caml_get_continuation_callstack() {
  return [0];
}

//Provides: caml_ml_condition_new
//Version: >= 5.0
function caml_ml_condition_new(_unit) {
  return { condition: 1 };
}

//Provides: caml_ml_condition_wait
//Version: >= 5.0
function caml_ml_condition_wait(_t, _mutext) {
  return 0;
}

//Provides: caml_ml_condition_broadcast
//Version: >= 5.0
function caml_ml_condition_broadcast(_t) {
  return 0;
}

//Provides: caml_ml_condition_signal
//Version: >= 5.0
function caml_ml_condition_signal(_t) {
  return 0;
}

//Provides: jsoo_effect_not_supported
//Requires: caml_failwith
//!If: effects
//Version: >= 5.0
function jsoo_effect_not_supported() {
  caml_failwith("Effect handlers are not supported");
}

//Provides: caml_resume
//Requires:caml_stack_depth, caml_call_gen_cps, caml_current_stack, caml_wrap_exception, caml_resume_stack
//If: effects
//If: doubletranslate
//Version: >= 5.0
function caml_resume(f, arg, stack, last) {
  var saved_stack_depth = caml_stack_depth;
  var saved_current_stack = caml_current_stack;
  try {
    caml_current_stack = { k: 0, x: 0, h: 0, e: 0 };
    var k = caml_resume_stack(stack, last, function (x) {
      return x;
    });
    /* Note: f is not an ordinary function but a (direct-style, CPS) closure pair */
    var res = { joo_tramp: f, joo_args: [arg, k], joo_direct: 0 };
    do {
      /* Avoids trampolining too often while still avoiding stack overflow. See
         [caml_callback]. */
      caml_stack_depth = 40;
      try {
        res = res.joo_direct
          ? res.joo_tramp.apply(null, res.joo_args)
          : caml_call_gen_cps(res.joo_tramp, res.joo_args);
      } catch (e) {
        /* Handle exception coming from JavaScript or from the runtime. */
        if (!caml_current_stack.x) throw e;
        var handler = caml_current_stack.x.h;
        caml_current_stack.x = caml_current_stack.x.t;
        res = {
          joo_tramp: handler,
          joo_args: [caml_wrap_exception(e)],
          joo_direct: 1,
        };
      }
    } while (res?.joo_args);
    return res;
  } finally {
    caml_stack_depth = saved_stack_depth;
    caml_current_stack = saved_current_stack;
  }
}

//Provides: caml_cps_closure
//If: effects
//If: doubletranslate
function caml_cps_closure(direct_f, cps_f) {
  direct_f.cps = cps_f;
  return direct_f;
}

//Provides: caml_assume_no_perform
//Requires: caml_callback
//If: effects
//If: !doubletranslate
function caml_assume_no_perform(f) {
  return caml_callback(f, [0]);
}