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
(** Provide utilities for {!Js.t} *)
module Internal = struct
module Registry = Ephemeron.K1.Make (struct
type t = Obj.t
let equal (left : t) (right : t) = left == right
let hash = Hashtbl.hash
end)
type entry = {
method_name : string;
js_name : string;
mutable present : bool;
get_boxed : unit -> Obj.t;
set_boxed : Obj.t -> unit;
}
type metadata = {
mutable order_rev : string list;
mutable cached_keys : string array option;
entries : (string, entry) Hashtbl.t;
}
type state = Built of metadata | Deferred of (unit -> entry list)
type slot = { mutable state : state }
let registry : slot Registry.t = Registry.create 16
let empty_metadata () = { order_rev = []; cached_keys = Some [||]; entries = Hashtbl.create 8 }
let add_key_in_order metadata js_name =
metadata.order_rev <- js_name :: metadata.order_rev;
metadata.cached_keys <- None
let keys_in_order metadata =
match metadata.cached_keys with
| Some keys -> keys
| None ->
let keys = Array.of_list (List.rev metadata.order_rev) in
metadata.cached_keys <- Some keys;
keys
let register_entry metadata entry =
let was_present =
match Hashtbl.find_opt metadata.entries entry.js_name with Some existing -> existing.present | None -> false
in
Hashtbl.replace metadata.entries entry.js_name entry;
if entry.present && not was_present then add_key_in_order metadata entry.js_name
let build_metadata slot entries =
let metadata = empty_metadata () in
List.iter (register_entry metadata) entries;
slot.state <- Built metadata;
metadata
let force slot = match slot.state with Built metadata -> metadata | Deferred thunk -> build_metadata slot (thunk ())
let get_metadata object_ =
match Registry.find_opt registry (Obj.repr object_) with None -> None | Some slot -> Some (force slot)
let ensure_metadata object_ =
match Registry.find_opt registry (Obj.repr object_) with
| Some slot -> force slot
| None ->
let metadata = empty_metadata () in
Registry.add registry (Obj.repr object_) { state = Built metadata };
metadata
let deferred_entry ~method_name ~js_name ~present cell =
{
method_name;
js_name;
present;
get_boxed = (fun () -> Obj.repr !cell);
set_boxed = (fun next -> cell := Obj.obj next);
}
let register_deferred object_ thunk =
Registry.replace registry (Obj.repr object_) { state = Deferred thunk };
object_
let clone_entry entry =
let cell = ref (entry.get_boxed ()) in
{
method_name = entry.method_name;
js_name = entry.js_name;
present = entry.present;
get_boxed = (fun () -> !cell);
set_boxed = (fun next -> cell := next);
}
let present_entries metadata =
Array.fold_right
(fun key acc ->
match Hashtbl.find_opt metadata.entries key with
| Some entry when entry.present -> entry :: acc
| Some _ | None -> acc)
(keys_in_order metadata) []
let build metadata =
let present_entries = present_entries metadata in
let object_ : < .. > =
match present_entries with
| [] -> ((object end : < >) :> < .. >)
| _ ->
let table =
CamlinternalOO.create_table (Array.of_list (List.map (fun entry -> entry.method_name) present_entries))
in
CamlinternalOO.init_class table;
let object_ = CamlinternalOO.create_object table in
List.iter
(fun entry ->
let label = CamlinternalOO.get_method_label table entry.method_name in
let closure : CamlinternalOO.meth =
Obj.obj (Obj.repr (fun (_self : CamlinternalOO.obj) -> entry.get_boxed ()))
in
CamlinternalOO.set_method table label closure)
present_entries;
CamlinternalOO.run_initializers object_ table;
Obj.obj (Obj.repr object_)
in
Registry.replace registry (Obj.repr object_) { state = Built metadata };
object_
let copy_present_entries_into target_metadata source =
match get_metadata source with
| None -> ()
| Some source_metadata ->
Array.iter
(fun key ->
match Hashtbl.find_opt source_metadata.entries key with
| None -> ()
| Some source_entry -> (
match Hashtbl.find_opt target_metadata.entries key with
| Some target_entry ->
target_entry.set_boxed (source_entry.get_boxed ());
if not target_entry.present then add_key_in_order target_metadata key;
target_entry.present <- true
| None ->
let cloned_entry = clone_entry source_entry in
Hashtbl.add target_metadata.entries key cloned_entry;
if cloned_entry.present then add_key_in_order target_metadata key))
(keys_in_order source_metadata)
let assign_into target source =
let target_metadata = ensure_metadata target in
copy_present_entries_into target_metadata source;
target
let register_deferred_abstract object_ thunk = Obj.obj (Obj.repr (register_deferred object_ thunk))
end
let empty () : < .. > = Internal.register_deferred_abstract ((object end : < >) :> < .. >) (fun () -> [])
let assign target source = Internal.assign_into target source
let merge () left right : < .. > =
let metadata = Internal.empty_metadata () in
Internal.copy_present_entries_into metadata left;
Internal.copy_present_entries_into metadata right;
let object_ : < .. > = Internal.build metadata in
Obj.obj (Obj.repr object_)
let keys object_ =
match Internal.get_metadata object_ with
| None -> [||]
| Some metadata -> Array.copy (Internal.keys_in_order metadata)