package eliom
Advanced client/server Web and mobile framework
Install
Dune Dependency
Authors
Maintainers
Sources
10.4.1.tar.gz
md5=218bcb5cd275cc05be06574c5fa357fa
sha512=edbf8b084ec1b7439d4715199c22eb925a77a2dcfbe76bb5bbc4b9d076b70be1b0de74f9eab9dfb7854df28d65eb31a4c17380528d4a461d9c2a4222abe804cc
doc/src/eliom.server/eliom_wrap.ml.html
Source file eliom_wrap.ml
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 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
# 1 "src/lib/eliom_wrap.server.ml" (* Ocsigen * Copyright (C) 2011 Pierre Chambart * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) let section = Lwt_log.Section.make "eliom:wrap" type poly external to_poly : 'a -> poly = "%identity" type 'a wrapped_value = poly * 'a let with_no_heap_compaction f v = let gc_control = Gc.get () in (* disable heap compaction *) Gc.set {gc_control with Gc.max_overhead = max_int}; match f v with | v -> (* reset gc settings *) Gc.set gc_control; v | exception e -> (* reset gc settings *) Gc.set gc_control; raise e module Mark : sig type t val wrap_mark : t val do_nothing_mark : t val unwrap_mark : t end = struct type t = string let wrap_mark = "wrap_mark" let do_nothing_mark = "do_nothing_mark" let unwrap_mark = "unwrap_mark" end type marked_value = {mark : Mark.t; f : (Obj.t -> Obj.t) option} [@@warning "-69"] let make_mark f mark = {mark; f} let is_marked o = let is_mark o = if Obj.tag o = 0 && Obj.size o = 2 && Obj.field o 0 == Obj.repr Mark.wrap_mark then ( let f = Obj.field o 1 in assert (Obj.tag f = 0); (* The case None should not happen here *) assert (Obj.size f = 1); assert ( let tag = Obj.tag (Obj.field f 0) in tag = Obj.infix_tag || tag = Obj.closure_tag); true) else false in if Obj.tag o = 0 && Obj.size o >= 2 (* WARNING: we only allow block values with tag = 0 to be wrapped. It is easier: we do not have to do another test to know if the value is a function *) then let potential_mark = Obj.field o (Obj.size o - 1) in is_mark potential_mark else false let wrap_locally o = let mark : marked_value = Obj.obj (Obj.field o (Obj.size o - 1)) in match mark.f with Some f -> f o | None -> assert false let bits = 8 (* We use a hash-table with open addressing (which minimize allocations) and resizable arrays. The initial size of the hash table is 2 ** bits; the initial size of arrays is half this *) let none = Obj.repr 0 (* Unallocated entry in an array or in a hash-table *) module DynArray = struct let rec check_size a i = let len = Array.length !a in if i > len then ( let old_a = !a in a := Array.make (2 * len) none; Array.blit old_a 0 !a 0 len; check_size a i) let make () = ref (Array.make (1 lsl (bits - 1)) none) let get a i = !a.(i) let set a i v = !a.(i) <- v end let resize_count = ref 0 let rehash_count = ref 0 (* Hash-table associating an integer to a block. As the block may be moved (once) during a minor garbage collection, we may allocate more than once index for a block. But thereafter a look-up will always return the second index. *) module Tbl = struct type t = { mutable size : int ; (* Size of the hash table *) mutable shift : int ; (* For hashing *) mutable occupancy : int ; (* How many elements have been inserted *) mutable obj : Obj.t array ; (* Inserted blocks *) mutable idx : int array ; (* Corresponding indices *) mutable gc : int ; (* Last minor GC cycle where the table was accurate *) on_resize : (int -> unit) list } (* Functions called on resize *) let cst = (* Fibonacci hash: 2 ^ Sys.int_size / phi *) Int64.to_int (Int64.shift_right 0x4F1BBCDCBFA53E09L (63 - Sys.int_size)) let hash tbl x = (Obj.magic x * cst) lsr tbl.shift let gc_count () = Gc.((quick_stat ()).minor_collections) (* Rehash the hash-table, possibly after resizing it *) let reallocate resize tbl = let old_size = tbl.size in let old_obj = tbl.obj in let old_idx = tbl.idx in if resize then ( tbl.size <- 2 * old_size; tbl.shift <- tbl.shift - 1; List.iter (fun f -> f (tbl.size lsr 1)) tbl.on_resize); tbl.obj <- Array.make tbl.size none; tbl.idx <- Array.make tbl.size (-1); tbl.gc <- gc_count (); let rec insert tbl h x idx = let y = tbl.obj.(h) in if y == none then ( tbl.obj.(h) <- x; tbl.idx.(h) <- idx) else if y == x then tbl.idx.(h) <- max idx tbl.idx.(h) (* Keep largest index *) else insert tbl ((h + 1) land (tbl.size - 1)) x idx in for i = 0 to old_size - 1 do let x = old_obj.(i) in if x != none then insert tbl (hash tbl x) x old_idx.(i) done let resize tbl = incr resize_count; reallocate true tbl let rehash tbl = incr rehash_count; reallocate false tbl let make tbls = let size = 1 lsl bits in let obj = Array.make size none in let idx = Array.make size (-1) in let on_resize = List.map DynArray.check_size tbls in let gc = gc_count () in {size; shift = Sys.int_size - bits; occupancy = 0; obj; idx; gc; on_resize} let rec allocate_rec tbl x i = if tbl.obj.(i) == x then tbl.idx.(i) else if tbl.obj.(i) == none then ( tbl.obj.(i) <- x; let idx = tbl.occupancy in tbl.idx.(i) <- idx; tbl.occupancy <- idx + 1; if tbl.occupancy * 2 >= tbl.size then resize tbl; idx) else allocate_rec tbl x ((i + 1) land (tbl.size - 1)) (* Insert a block into the hash-table. This may return a new index if the block was moved. *) let allocate_index tbl x = allocate_rec tbl x (hash tbl x) let rec get_rec tbl x i = let y = tbl.obj.(i) in if y == x then tbl.idx.(i) else if y == none then -1 (* Not found *) else get_rec tbl x ((i + 1) land (tbl.size - 1)) (* This may fail if a GC occurred *) let get_index_no_retry tbl x = get_rec tbl x (hash tbl x) (* Get the index associated to a block already in the hash-table. If allocate_index is not invoked in-between, this always returns the same index for a given block. Indeed, a look-up always return the largest index of a block; this property is preserved both when invoking allocate_index (though this may allocate a larger index) and by rehashing. *) let get_index tbl x = let idx = get_index_no_retry tbl x in if idx <> -1 then idx else ( rehash tbl; let idx = get_index_no_retry tbl x in if idx = -1 then ( for i = 0 to Array.length tbl.obj - 1 do assert (tbl.obj.(i) != x) done; Format.eprintf "%b@." (is_marked x)); assert (idx <> -1); idx) (* We can check whether the table is up to date, but this has a very slight chance to perform an allocation; in which case, the table will no longer be up to date... *) let was_up_to_date tbl = tbl.gc = gc_count () end let obj_kind v = if not (Obj.is_block v) then `Opaque else let tag = Obj.tag v in if tag >= Obj.no_scan_tag then `Opaque else if tag <= Obj.last_non_constant_constructor_tag then `Scannable else if tag = Obj.forward_tag then let tag' = Obj.tag (Obj.field v 0) in if tag' = Obj.forward_tag || tag' = Obj.double_tag then `Scannable else (* Forward pointer that may be optimized away by the GC *) `Forward else ( if tag = Obj.lazy_tag then failwith "lazy values must be forced before wrapping"; if tag = Obj.object_tag then failwith "cannot wrap object values"; if tag = Obj.closure_tag then failwith "cannot wrap functional values"; if tag = Obj.infix_tag then failwith "cannot wrap functional values: infix tag"; (* Should not happen (in case a new kind of value is added) *) failwith (Printf.sprintf "cannot wrap value (unexpected tag %d)" tag)) let unchanged = (* This block and its descendants can be left unchanged *) Obj.repr 1 let modified = (* This block or its descendants may need to be modified *) Obj.repr 2 let iteration_count = ref 0 let wrap_count = ref 0 (* First step: we traverse the value and find which parts need to be replaced. We also compute which parts can be clearly left unchanged. We may traverse some values twice if a minor GC occurs, but this is harmless. *) (* TODO: shall we use an explicit stack to avoid stack overflows? *) let rec find_substs tbl subst_tbl v = incr iteration_count; match obj_kind v with | `Opaque -> (* Opaque values don't need to be copied *) unchanged | `Forward -> (* Follow the forward pointers that may disappear due to GC (our code might get confused if we stored them in the hash-table) *) find_substs tbl subst_tbl (Obj.field v 0) | `Scannable -> let idx = Tbl.allocate_index tbl v in let v' = DynArray.get subst_tbl idx in if v' == none (* Not visited yet *) then if is_marked v then if not (Tbl.was_up_to_date tbl) then ( (* v may have been visited already, so we rehash and try again. Indeed, we don't want to call the wrapping function twice on the same value. *) Tbl.rehash tbl; find_substs tbl subst_tbl v) else ( incr wrap_count; let v' = wrap_locally v in DynArray.set subst_tbl idx v'; ignore (find_substs tbl subst_tbl v'); modified) else ( (* We don't know yet whether v needs to be copied. We conservatively assume so for now. *) DynArray.set subst_tbl idx modified; let size = Obj.size v in let is_unchanged = ref true in for i = 0 to size - 1 do let status = find_substs tbl subst_tbl (Obj.field v i) in is_unchanged := !is_unchanged && status == unchanged done; let res = if !is_unchanged then unchanged else modified in DynArray.set subst_tbl idx res; res) else v' let copy_count = ref 0 let rec duplicate tbl subst_tbl copy_tbl orig = match obj_kind orig with | `Opaque -> (* Opaque values are not copied *) orig | `Forward -> (* Follow forward pointers that may disappear due to GC *) duplicate tbl subst_tbl copy_tbl (Obj.field orig 0) | `Scannable -> let idx = Tbl.get_index tbl orig in let subst = DynArray.get subst_tbl idx in if subst == unchanged then (* This block does not need to be copied *) orig else if subst != modified then (* This block is replaced by another value *) duplicate tbl subst_tbl copy_tbl subst else let copy = DynArray.get copy_tbl idx in if copy != none then (* Since we have already copied the block; return the copy *) copy else ( incr copy_count; let copy = Obj.dup orig in DynArray.set copy_tbl idx copy; let size = Obj.size orig in for i = 0 to size - 1 do let child = Obj.field orig i in let child_copy = duplicate tbl subst_tbl copy_tbl child in if child_copy != child then Obj.set_field copy i child_copy done; copy) let perform_wrap = with_no_heap_compaction @@ fun v -> iteration_count := 0; copy_count := 0; wrap_count := 0; resize_count := 0; rehash_count := 0; (* TODO: maybe we should use globally allocated tables by default, with temporary allocations only for really large values? *) let subst_tbl = DynArray.make () in let copy_tbl = DynArray.make () in let tbl = Tbl.make [subst_tbl; copy_tbl] in ignore (find_substs tbl subst_tbl v); let w = duplicate tbl subst_tbl copy_tbl v in Lwt_log.ign_debug_f ~section "Wrap stats: %d visited (%d blocks), %d wrapped, %d copied, %d resizes, %d rehashes" !iteration_count tbl.occupancy !wrap_count !copy_count !resize_count !rehash_count; w type +'a wrapper = marked_value let create_wrapper (f : 'a -> 'b) : 'a wrapper = make_mark (Some (fun x -> Obj.repr (f (Obj.obj x)))) Mark.wrap_mark let empty_wrapper : 'a wrapper = make_mark None Mark.do_nothing_mark type unwrap_id = int let id_of_int x = x type unwrapper = { (* WARNING Must be the same as Eliom_unwrap.unwrapper *) id : unwrap_id ; umark : Mark.t } [@@warning "-69"] let create_unwrapper id = {id; umark = Mark.unwrap_mark} let empty_unwrapper = {id = -1; umark = Mark.do_nothing_mark} let wrap v = to_poly Mark.unwrap_mark, Obj.obj (perform_wrap (Obj.repr v))
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>